def build_db_store( self, db_config: DatabaseConnectionConfig, allow_outdated_version: bool = False, ) -> Store: """Builds and returns a database store using the provided configuration. Args: db_config: The database configuration allow_outdated_version: True to suppress errors about the database server version being too old to run a complete synapse Returns: The built Store object. """ self.progress.set_state("Preparing %s" % db_config.config["name"]) engine = create_engine(db_config.config) hs = MockHomeserver(self.hs_config) with make_conn(db_config, engine, "portdb") as db_conn: engine.check_database( db_conn, allow_outdated_version=allow_outdated_version) prepare_database(db_conn, engine, config=self.hs_config) # Type safety: ignore that we're using Mock homeservers here. store = Store(DatabasePool(hs, db_config, engine), db_conn, hs) # type: ignore[arg-type] db_conn.commit() return store
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)
def on_new_connection(self, db_conn): if self._is_in_memory: # In memory databases need to be rebuilt each time. Ideally we'd # reuse the same connection as we do when starting up, but that # would involve using adbapi before we have started the reactor. prepare_database(db_conn, self, config=None) db_conn.create_function("rank", 1, _rank)
def __init__(self, main_store_class, hs): # Note we pass in the main store class here as workers use a different main # store. self.databases = [] self.main = None self.state = None for database_config in hs.config.database.databases: db_name = database_config.name engine = create_engine(database_config.config) with make_conn(database_config, engine) as db_conn: logger.info("Preparing database %r...", db_name) engine.check_database(db_conn.cursor()) prepare_database( db_conn, engine, hs.config, data_stores=database_config.data_stores, ) database = Database(hs, database_config, engine) if "main" in database_config.data_stores: logger.info("Starting 'main' data store") # Sanity check we don't try and configure the main store on # multiple databases. if self.main: raise Exception("'main' data store already configured") self.main = main_store_class(database, db_conn, hs) if "state" in database_config.data_stores: logger.info("Starting 'state' data store") # Sanity check we don't try and configure the state store on # multiple databases. if self.state: raise Exception( "'state' data store already configured") self.state = StateGroupDataStore(database, db_conn, hs) db_conn.commit() self.databases.append(database) logger.info("Database %r prepared", db_name) # Sanity check that we have actually configured all the required stores. if not self.main: raise Exception("No 'main' data store configured") if not self.state: raise Exception("No 'main' data store configured")
def __init__(self, main_store_class, db_conn, hs): # Note we pass in the main store class here as workers use a different main # store. database = Database(hs) # Check that db is correctly configured. database.engine.check_database(db_conn.cursor()) prepare_database(db_conn, database.engine, config=hs.config) self.main = main_store_class(database, db_conn, hs)
def on_new_connection(self, db_conn): # We need to import here to avoid an import loop. from synapse.storage.prepare_database import prepare_database if self._is_in_memory: # In memory databases need to be rebuilt each time. Ideally we'd # reuse the same connection as we do when starting up, but that # would involve using adbapi before we have started the reactor. prepare_database(db_conn, self, config=None) db_conn.create_function("rank", 1, _rank) db_conn.execute("PRAGMA foreign_keys = ON;")
def setupdb(): # If we're using PostgreSQL, set up the db once if USE_POSTGRES_FOR_TESTS: # create a PostgresEngine db_engine = create_engine({"name": "psycopg2", "args": {}}) # connect to postgres to create the base database. db_conn = db_engine.module.connect( user=POSTGRES_USER, host=POSTGRES_HOST, port=POSTGRES_PORT, password=POSTGRES_PASSWORD, dbname=POSTGRES_DBNAME_FOR_INITIAL_CREATE, ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (POSTGRES_BASE_DB, )) cur.execute( "CREATE DATABASE %s ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' " "template=template0;" % (POSTGRES_BASE_DB, )) cur.close() db_conn.close() # Set up in the db db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER, host=POSTGRES_HOST, port=POSTGRES_PORT, password=POSTGRES_PASSWORD, ) db_conn = LoggingDatabaseConnection(db_conn, db_engine, "tests") prepare_database(db_conn, db_engine, None) db_conn.close() def _cleanup(): db_conn = db_engine.module.connect( user=POSTGRES_USER, host=POSTGRES_HOST, port=POSTGRES_PORT, password=POSTGRES_PASSWORD, dbname=POSTGRES_DBNAME_FOR_INITIAL_CREATE, ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (POSTGRES_BASE_DB, )) cur.close() db_conn.close() atexit.register(_cleanup)
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)
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)
def prepare_database(self, db_conn): prepare_sqlite3_database(db_conn) prepare_database(db_conn, self, config=self.config)
def setup_test_homeserver(name="test", datastore=None, config=None, **kargs): """Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied a datastore backed by an in-memory sqlite db will be given to the HS. """ if config is None: config = Mock() config.signing_key = [MockKey()] config.event_cache_size = 1 config.enable_registration = True config.macaroon_secret_key = "not even a little secret" config.expire_access_token = False config.server_name = name config.trusted_third_party_id_servers = [] config.room_invite_state_types = [] config.password_providers = [] config.worker_replication_url = "" config.worker_app = None config.email_enable_notifs = False config.block_non_admin_invites = False config.federation_domain_whitelist = None config.user_directory_search_all_users = False # disable user directory updates, because they get done in the # background, which upsets the test runner. config.update_user_directory = False config.use_frozen_dicts = True config.ldap_enabled = False if "clock" not in kargs: kargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: config.database_config = { "name": "psycopg2", "args": { "database": "synapse_test", "cp_min": 1, "cp_max": 5, }, } else: config.database_config = { "name": "sqlite3", "args": { "database": ":memory:", "cp_min": 1, "cp_max": 1, }, } db_engine = create_engine(config.database_config) # we need to configure the connection pool to run the on_new_connection # function, so that we can test code that uses custom sqlite functions # (like rank). config.database_config["args"]["cp_openfun"] = db_engine.on_new_connection if datastore is None: hs = HomeServer(name, config=config, db_config=config.database_config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), **kargs) db_conn = hs.get_db_conn() # make sure that the database is empty if isinstance(db_engine, PostgresEngine): cur = db_conn.cursor() cur.execute( "SELECT tablename FROM pg_tables where schemaname='public'") rows = cur.fetchall() for r in rows: cur.execute("DROP TABLE %s CASCADE" % r[0]) yield prepare_database(db_conn, db_engine, config) hs.setup() else: hs = HomeServer(name, db_pool=None, datastore=datastore, config=config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), **kargs) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) hs.get_auth_handler().hash = lambda p: hashlib.md5(p).hexdigest() hs.get_auth_handler().validate_hash = lambda p, h: hashlib.md5( p).hexdigest() == h fed = kargs.get("resource_for_federation", None) if fed: server.register_servlets( hs, resource=fed, authenticator=server.Authenticator(hs), ratelimiter=FederationRateLimiter( hs.get_clock(), window_size=hs.config.federation_rc_window_size, sleep_limit=hs.config.federation_rc_sleep_limit, sleep_msec=hs.config.federation_rc_sleep_delay, reject_limit=hs.config.federation_rc_reject_limit, concurrent_requests=hs.config.federation_rc_concurrent), ) defer.returnValue(hs)
def __init__(self, main_store_class, hs): # Note we pass in the main store class here as workers use a different main # store. self.databases = [] main = None state = None persist_events = None for database_config in hs.config.database.databases: db_name = database_config.name engine = create_engine(database_config.config) with make_conn(database_config, engine) as db_conn: logger.info("Preparing database %r...", db_name) engine.check_database(db_conn) prepare_database( db_conn, engine, hs.config, databases=database_config.databases, ) database = DatabasePool(hs, database_config, engine) if "main" in database_config.databases: logger.info("Starting 'main' data store") # Sanity check we don't try and configure the main store on # multiple databases. if main: raise Exception("'main' data store already configured") main = main_store_class(database, db_conn, hs) # If we're on a process that can persist events also # instantiate a `PersistEventsStore` if hs.config.worker.writers.events == hs.get_instance_name( ): persist_events = PersistEventsStore(hs, database, main) if "state" in database_config.databases: logger.info("Starting 'state' data store") # Sanity check we don't try and configure the state store on # multiple databases. if state: raise Exception( "'state' data store already configured") state = StateGroupDataStore(database, db_conn, hs) db_conn.commit() self.databases.append(database) logger.info("Database %r prepared", db_name) # Sanity check that we have actually configured all the required stores. if not main: raise Exception("No 'main' data store configured") if not state: raise Exception("No 'main' data store configured") # We use local variables here to ensure that the databases do not have # optional types. self.main = main self.state = state self.persist_events = persist_events
def get_db_conn(self): conn = self.connect() engine = self.create_engine() prepare_database(conn, engine, self.config) return conn
def get_db_conn(self): conn = self.connect() engine = create_engine("sqlite3") prepare_database(conn, engine) return conn
def prepare(self): engine = create_engine("sqlite3") return self.runWithConnection( lambda conn: prepare_database(conn, engine) )
def prepare_database(self, db_conn): prepare_sqlite3_database(db_conn) prepare_database(db_conn, self)
def prepare_database(self, db_conn): prepare_database(db_conn, self)
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + str(e) + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) synapse.config.logger.setup_logging(config, use_worker_options=False) events.USE_FROZEN_DICTS = config.use_frozen_dicts tls_server_context_factory = context_factory.ServerContextFactory(config) tls_client_options_factory = context_factory.ClientTLSOptionsFactory( config) database_engine = create_engine(config.database_config) config.database_config["args"][ "cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, tls_server_context_factory=tls_server_context_factory, tls_client_options_factory=tls_client_options_factory, config=config, version_string="Synapse/" + get_version_string(synapse), database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: with hs.get_db_conn(run_new_connection=False) as db_conn: prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n") sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() hs.start_listening() def start(): hs.get_pusherpool().start() hs.get_datastore().start_profiling() hs.get_datastore().start_doing_background_updates() reactor.callWhenRunning(start) return hs
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_config("Synapse Homeserver", config_options, generate_section="Homeserver") except ConfigError as e: sys.stderr.write("\n" + e.message + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) config.setup_logging() # check any extra requirements we have now we have a config check_requirements(config) version_string = get_version_string("Synapse", synapse) logger.info("Server hostname: %s", config.server_name) logger.info("Server version: %s", version_string) events.USE_FROZEN_DICTS = config.use_frozen_dicts tls_server_context_factory = context_factory.ServerContextFactory(config) database_engine = create_engine(config.database_config) config.database_config["args"][ "cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, tls_server_context_factory=tls_server_context_factory, config=config, content_addr=config.content_addr, version_string=version_string, database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: db_conn = hs.get_db_conn(run_new_connection=False) prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n") sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() hs.start_listening() def start(): hs.get_pusherpool().start() hs.get_state_handler().start_caching() hs.get_datastore().start_profiling() hs.get_datastore().start_doing_background_updates() hs.get_replication_layer().start_get_pdu_cache() reactor.callWhenRunning(start) return hs
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + str(e) + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) synapse.config.logger.setup_logging( config, use_worker_options=False ) events.USE_FROZEN_DICTS = config.use_frozen_dicts database_engine = create_engine(config.database_config) config.database_config["args"]["cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, config=config, version_string="Synapse/" + get_version_string(synapse), database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: with hs.get_db_conn(run_new_connection=False) as db_conn: prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n" ) sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() hs.setup_master() @defer.inlineCallbacks def do_acme(): """ Reprovision an ACME certificate, if it's required. Returns: Deferred[bool]: Whether the cert has been updated. """ acme = hs.get_acme_handler() # Check how long the certificate is active for. cert_days_remaining = hs.config.is_disk_cert_valid( allow_self_signed=False ) # We want to reprovision if cert_days_remaining is None (meaning no # certificate exists), or the days remaining number it returns # is less than our re-registration threshold. provision = False if ( cert_days_remaining is None or cert_days_remaining < hs.config.acme_reprovision_threshold ): provision = True if provision: yield acme.provision_certificate() defer.returnValue(provision) @defer.inlineCallbacks def reprovision_acme(): """ Provision a certificate from ACME, if required, and reload the TLS certificate if it's renewed. """ reprovisioned = yield do_acme() if reprovisioned: _base.refresh_certificate(hs) @defer.inlineCallbacks def start(): try: # Run the ACME provisioning code, if it's enabled. if hs.config.acme_enabled: acme = hs.get_acme_handler() # Start up the webservices which we will respond to ACME # challenges with, and then provision. yield acme.start_listening() yield do_acme() # Check if it needs to be reprovisioned every day. hs.get_clock().looping_call( reprovision_acme, 24 * 60 * 60 * 1000 ) _base.start(hs, config.listeners) hs.get_pusherpool().start() hs.get_datastore().start_doing_background_updates() except Exception: # Print the exception and bail out. print("Error during startup:", file=sys.stderr) # this gives better tracebacks than traceback.print_exc() Failure().printTraceback(file=sys.stderr) if reactor.running: reactor.stop() sys.exit(1) reactor.callWhenRunning(start) return hs
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + str(e) + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) synapse.config.logger.setup_logging(config, use_worker_options=False) events.USE_FROZEN_DICTS = config.use_frozen_dicts tls_server_context_factory = context_factory.ServerContextFactory(config) tls_client_options_factory = context_factory.ClientTLSOptionsFactory(config) database_engine = create_engine(config.database_config) config.database_config["args"]["cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, tls_server_context_factory=tls_server_context_factory, tls_client_options_factory=tls_client_options_factory, config=config, version_string="Synapse/" + get_version_string(synapse), database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: with hs.get_db_conn(run_new_connection=False) as db_conn: prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n" ) sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() hs.start_listening() def start(): hs.get_pusherpool().start() hs.get_datastore().start_profiling() hs.get_datastore().start_doing_background_updates() reactor.callWhenRunning(start) return hs
def on_new_connection(self, db_conn): prepare_database(db_conn, self, config=None) db_conn.create_function("rank", 1, _rank)
def prepare(self): engine = self.create_engine() return self.runWithConnection( lambda conn: prepare_database(conn, engine, self.config))
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + str(e) + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) sighup_callbacks = [] synapse.config.logger.setup_logging( config, use_worker_options=False, register_sighup=sighup_callbacks.append ) def handle_sighup(*args, **kwargs): for i in sighup_callbacks: i(*args, **kwargs) if hasattr(signal, "SIGHUP"): signal.signal(signal.SIGHUP, handle_sighup) events.USE_FROZEN_DICTS = config.use_frozen_dicts database_engine = create_engine(config.database_config) config.database_config["args"]["cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, config=config, version_string="Synapse/" + get_version_string(synapse), database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: with hs.get_db_conn(run_new_connection=False) as db_conn: prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n" ) sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() def refresh_certificate(*args): """ Refresh the TLS certificates that Synapse is using by re-reading them from disk and updating the TLS context factories to use them. """ logging.info("Reloading certificate from disk...") hs.config.read_certificate_from_disk() hs.tls_server_context_factory = context_factory.ServerContextFactory(config) hs.tls_client_options_factory = context_factory.ClientTLSOptionsFactory( config ) logging.info("Certificate reloaded.") logging.info("Updating context factories...") for i in hs._listening_services: if isinstance(i.factory, TLSMemoryBIOFactory): i.factory = TLSMemoryBIOFactory( hs.tls_server_context_factory, False, i.factory.wrappedFactory ) logging.info("Context factories updated.") sighup_callbacks.append(refresh_certificate) @defer.inlineCallbacks def start(): try: # Check if the certificate is still valid. cert_days_remaining = hs.config.is_disk_cert_valid() if hs.config.acme_enabled: # If ACME is enabled, we might need to provision a certificate # before starting. acme = hs.get_acme_handler() # Start up the webservices which we will respond to ACME # challenges with. yield acme.start_listening() # We want to reprovision if cert_days_remaining is None (meaning no # certificate exists), or the days remaining number it returns # is less than our re-registration threshold. if (cert_days_remaining is None) or ( not cert_days_remaining > hs.config.acme_reprovision_threshold ): yield acme.provision_certificate() # Read the certificate from disk and build the context factories for # TLS. hs.config.read_certificate_from_disk() hs.tls_server_context_factory = context_factory.ServerContextFactory(config) hs.tls_client_options_factory = context_factory.ClientTLSOptionsFactory( config ) # It is now safe to start your Synapse. hs.start_listening() hs.get_pusherpool().start() hs.get_datastore().start_profiling() hs.get_datastore().start_doing_background_updates() except Exception as e: # If a DeferredList failed (like in listening on the ACME listener), # we need to print the subfailure explicitly. if isinstance(e, defer.FirstError): e.subFailure.printTraceback(sys.stderr) sys.exit(1) # Something else went wrong when starting. Print it and bail out. traceback.print_exc(file=sys.stderr) sys.exit(1) reactor.callWhenRunning(start) return hs
def setup_test_homeserver( cleanup_func, name="test", datastore=None, config=None, reactor=None, homeserverToUse=TestHomeServer, **kargs ): """ Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied, one is created and given to the homeserver. Args: cleanup_func : The function used to register a cleanup routine for after the test. """ if reactor is None: from twisted.internet import reactor if config is None: config = Mock() config.signing_key = [MockKey()] config.event_cache_size = 1 config.enable_registration = True config.macaroon_secret_key = "not even a little secret" config.expire_access_token = False config.server_name = name config.trusted_third_party_id_servers = [] config.room_invite_state_types = [] config.password_providers = [] config.worker_replication_url = "" config.worker_app = None config.email_enable_notifs = False config.block_non_admin_invites = False config.federation_domain_whitelist = None config.federation_rc_reject_limit = 10 config.federation_rc_sleep_limit = 10 config.federation_rc_sleep_delay = 100 config.federation_rc_concurrent = 10 config.filter_timeline_limit = 5000 config.user_directory_search_all_users = False config.user_consent_server_notice_content = None config.block_events_without_consent_error = None config.media_storage_providers = [] config.auto_join_rooms = [] config.limit_usage_by_mau = False config.hs_disabled = False config.hs_disabled_message = "" config.hs_disabled_limit_type = "" config.max_mau_value = 50 config.mau_trial_days = 0 config.mau_limits_reserved_threepids = [] config.admin_contact = None config.rc_messages_per_second = 10000 config.rc_message_burst_count = 10000 # we need a sane default_room_version, otherwise attempts to create rooms will # fail. config.default_room_version = "1" # disable user directory updates, because they get done in the # background, which upsets the test runner. config.update_user_directory = False def is_threepid_reserved(threepid): return ServerConfig.is_threepid_reserved(config, threepid) config.is_threepid_reserved.side_effect = is_threepid_reserved config.use_frozen_dicts = True config.ldap_enabled = False if "clock" not in kargs: kargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: test_db = "synapse_test_%s" % uuid.uuid4().hex config.database_config = { "name": "psycopg2", "args": {"database": test_db, "cp_min": 1, "cp_max": 5}, } else: config.database_config = { "name": "sqlite3", "args": {"database": ":memory:", "cp_min": 1, "cp_max": 1}, } db_engine = create_engine(config.database_config) # Create the database before we actually try and connect to it, based off # the template database we generate in setupdb() if datastore is None and isinstance(db_engine, PostgresEngine): db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db,)) cur.execute( "CREATE DATABASE %s WITH TEMPLATE %s;" % (test_db, POSTGRES_BASE_DB) ) cur.close() db_conn.close() # we need to configure the connection pool to run the on_new_connection # function, so that we can test code that uses custom sqlite functions # (like rank). config.database_config["args"]["cp_openfun"] = db_engine.on_new_connection if datastore is None: hs = homeserverToUse( name, config=config, db_config=config.database_config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs ) # Prepare the DB on SQLite -- PostgreSQL is a copy of an already up to # date db if not isinstance(db_engine, PostgresEngine): db_conn = hs.get_db_conn() yield prepare_database(db_conn, db_engine, config) db_conn.commit() db_conn.close() else: # We need to do cleanup on PostgreSQL def cleanup(): # Close all the db pools hs.get_db_pool().close() # Drop the test database db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db,)) db_conn.commit() cur.close() db_conn.close() if not LEAVE_DB: # Register the cleanup hook cleanup_func(cleanup) hs.setup() else: hs = homeserverToUse( name, db_pool=None, datastore=datastore, config=config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs ) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) hs.get_auth_handler().hash = lambda p: hashlib.md5(p.encode('utf8')).hexdigest() hs.get_auth_handler().validate_hash = ( lambda p, h: hashlib.md5(p.encode('utf8')).hexdigest() == h ) fed = kargs.get("resource_for_federation", None) if fed: server.register_servlets( hs, resource=fed, authenticator=server.Authenticator(hs), ratelimiter=FederationRateLimiter( hs.get_clock(), window_size=hs.config.federation_rc_window_size, sleep_limit=hs.config.federation_rc_sleep_limit, sleep_msec=hs.config.federation_rc_sleep_delay, reject_limit=hs.config.federation_rc_reject_limit, concurrent_requests=hs.config.federation_rc_concurrent, ), ) defer.returnValue(hs)
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + str(e) + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) synapse.config.logger.setup_logging(config, use_worker_options=False) events.USE_FROZEN_DICTS = config.use_frozen_dicts database_engine = create_engine(config.database_config) config.database_config["args"][ "cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, config=config, version_string="Synapse/" + get_version_string(synapse), database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: with hs.get_db_conn(run_new_connection=False) as db_conn: prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n") sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() @defer.inlineCallbacks def do_acme(): """ Reprovision an ACME certificate, if it's required. Returns: Deferred[bool]: Whether the cert has been updated. """ acme = hs.get_acme_handler() # Check how long the certificate is active for. cert_days_remaining = hs.config.is_disk_cert_valid( allow_self_signed=False) # We want to reprovision if cert_days_remaining is None (meaning no # certificate exists), or the days remaining number it returns # is less than our re-registration threshold. provision = False if (cert_days_remaining is None or cert_days_remaining < hs.config.acme_reprovision_threshold): provision = True if provision: yield acme.provision_certificate() defer.returnValue(provision) @defer.inlineCallbacks def reprovision_acme(): """ Provision a certificate from ACME, if required, and reload the TLS certificate if it's renewed. """ reprovisioned = yield do_acme() if reprovisioned: _base.refresh_certificate(hs) @defer.inlineCallbacks def start(): try: # Run the ACME provisioning code, if it's enabled. if hs.config.acme_enabled: acme = hs.get_acme_handler() # Start up the webservices which we will respond to ACME # challenges with, and then provision. yield acme.start_listening() yield do_acme() # Check if it needs to be reprovisioned every day. hs.get_clock().looping_call(reprovision_acme, 24 * 60 * 60 * 1000) _base.start(hs, config.listeners) hs.get_pusherpool().start() hs.get_datastore().start_doing_background_updates() except Exception: # Print the exception and bail out. print("Error during startup:", file=sys.stderr) # this gives better tracebacks than traceback.print_exc() Failure().printTraceback(file=sys.stderr) if reactor.running: reactor.stop() sys.exit(1) reactor.callWhenRunning(start) return hs
def prepare(self): engine = create_engine("sqlite3") return self.runWithConnection( lambda conn: prepare_database(conn, engine))
def setup_test_homeserver( cleanup_func, name="test", datastore=None, config=None, reactor=None, homeserverToUse=TestHomeServer, **kargs ): """ Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied, one is created and given to the homeserver. Args: cleanup_func : The function used to register a cleanup routine for after the test. """ if reactor is None: from twisted.internet import reactor if config is None: config = default_config(name) config.ldap_enabled = False if "clock" not in kargs: kargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: test_db = "synapse_test_%s" % uuid.uuid4().hex config.database_config = { "name": "psycopg2", "args": {"database": test_db, "cp_min": 1, "cp_max": 5}, } else: config.database_config = { "name": "sqlite3", "args": {"database": ":memory:", "cp_min": 1, "cp_max": 1}, } db_engine = create_engine(config.database_config) # Create the database before we actually try and connect to it, based off # the template database we generate in setupdb() if datastore is None and isinstance(db_engine, PostgresEngine): db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db,)) cur.execute( "CREATE DATABASE %s WITH TEMPLATE %s;" % (test_db, POSTGRES_BASE_DB) ) cur.close() db_conn.close() # we need to configure the connection pool to run the on_new_connection # function, so that we can test code that uses custom sqlite functions # (like rank). config.database_config["args"]["cp_openfun"] = db_engine.on_new_connection if datastore is None: hs = homeserverToUse( name, config=config, db_config=config.database_config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs ) # Prepare the DB on SQLite -- PostgreSQL is a copy of an already up to # date db if not isinstance(db_engine, PostgresEngine): db_conn = hs.get_db_conn() yield prepare_database(db_conn, db_engine, config) db_conn.commit() db_conn.close() else: # We need to do cleanup on PostgreSQL def cleanup(): import psycopg2 # Close all the db pools hs.get_db_pool().close() dropped = False # Drop the test database db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER ) db_conn.autocommit = True cur = db_conn.cursor() # Try a few times to drop the DB. Some things may hold on to the # database for a few more seconds due to flakiness, preventing # us from dropping it when the test is over. If we can't drop # it, warn and move on. for x in range(5): try: cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db,)) db_conn.commit() dropped = True except psycopg2.OperationalError as e: warnings.warn( "Couldn't drop old db: " + str(e), category=UserWarning ) time.sleep(0.5) cur.close() db_conn.close() if not dropped: warnings.warn("Failed to drop old DB.", category=UserWarning) if not LEAVE_DB: # Register the cleanup hook cleanup_func(cleanup) hs.setup() else: hs = homeserverToUse( name, db_pool=None, datastore=datastore, config=config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs ) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) hs.get_auth_handler().hash = lambda p: hashlib.md5(p.encode('utf8')).hexdigest() hs.get_auth_handler().validate_hash = ( lambda p, h: hashlib.md5(p.encode('utf8')).hexdigest() == h ) fed = kargs.get("resource_for_federation", None) if fed: server.register_servlets( hs, resource=fed, authenticator=server.Authenticator(hs), ratelimiter=FederationRateLimiter( hs.get_clock(), window_size=hs.config.federation_rc_window_size, sleep_limit=hs.config.federation_rc_sleep_limit, sleep_msec=hs.config.federation_rc_sleep_delay, reject_limit=hs.config.federation_rc_reject_limit, concurrent_requests=hs.config.federation_rc_concurrent, ), ) defer.returnValue(hs)
def __init__(self, main_store_class, hs): # Note we pass in the main store class here as workers use a different main # store. self.databases = [] main = None state = None persist_events = None for database_config in hs.config.database.databases: db_name = database_config.name engine = create_engine(database_config.config) with make_conn(database_config, engine, "startup") as db_conn: logger.info("[database config %r]: Checking database server", db_name) engine.check_database(db_conn) logger.info( "[database config %r]: Preparing for databases %r", db_name, database_config.databases, ) prepare_database( db_conn, engine, hs.config, databases=database_config.databases, ) database = DatabasePool(hs, database_config, engine) if "main" in database_config.databases: logger.info( "[database config %r]: Starting 'main' database", db_name) # Sanity check we don't try and configure the main store on # multiple databases. if main: raise Exception("'main' data store already configured") main = main_store_class(database, db_conn, hs) # If we're on a process that can persist events also # instantiate a `PersistEventsStore` if hs.get_instance_name( ) in hs.config.worker.writers.events: persist_events = PersistEventsStore(hs, database, main) if "state" in database_config.databases: logger.info( "[database config %r]: Starting 'state' database", db_name) # Sanity check we don't try and configure the state store on # multiple databases. if state: raise Exception( "'state' data store already configured") state = StateGroupDataStore(database, db_conn, hs) db_conn.commit() self.databases.append(database) logger.info("[database config %r]: prepared", db_name) # Closing the context manager doesn't close the connection. # psycopg will close the connection when the object gets GCed, but *only* # if the PID is the same as when the connection was opened [1], and # it may not be if we fork in the meantime. # # [1]: https://github.com/psycopg/psycopg2/blob/2_8_5/psycopg/connection_type.c#L1378 db_conn.close() # Sanity check that we have actually configured all the required stores. if not main: raise Exception("No 'main' database configured") if not state: raise Exception("No 'state' database configured") # We use local variables here to ensure that the databases do not have # optional types. self.main = main self.state = state self.persist_events = persist_events
def setup(config_options): """ Args: config_options_options: The options passed to Synapse. Usually `sys.argv[1:]`. Returns: HomeServer """ try: config = HomeServerConfig.load_or_generate_config( "Synapse Homeserver", config_options, ) except ConfigError as e: sys.stderr.write("\n" + e.message + "\n") sys.exit(1) if not config: # If a config isn't returned, and an exception isn't raised, we're just # generating config files and shouldn't try to continue. sys.exit(0) config.setup_logging() # check any extra requirements we have now we have a config check_requirements(config) version_string = get_version_string("Synapse", synapse) logger.info("Server hostname: %s", config.server_name) logger.info("Server version: %s", version_string) events.USE_FROZEN_DICTS = config.use_frozen_dicts tls_server_context_factory = context_factory.ServerContextFactory(config) database_engine = create_engine(config.database_config) config.database_config["args"]["cp_openfun"] = database_engine.on_new_connection hs = SynapseHomeServer( config.server_name, db_config=config.database_config, tls_server_context_factory=tls_server_context_factory, config=config, content_addr=config.content_addr, version_string=version_string, database_engine=database_engine, ) logger.info("Preparing database: %s...", config.database_config['name']) try: db_conn = hs.get_db_conn(run_new_connection=False) prepare_database(db_conn, database_engine, config=config) database_engine.on_new_connection(db_conn) hs.run_startup_checks(db_conn, database_engine) db_conn.commit() except UpgradeDatabaseException: sys.stderr.write( "\nFailed to upgrade database.\n" "Have you checked for version specific instructions in" " UPGRADES.rst?\n" ) sys.exit(1) logger.info("Database prepared in %s.", config.database_config['name']) hs.setup() hs.start_listening() def start(): hs.get_pusherpool().start() hs.get_state_handler().start_caching() hs.get_datastore().start_profiling() hs.get_datastore().start_doing_background_updates() hs.get_replication_layer().start_get_pdu_cache() reactor.callWhenRunning(start) return hs
def prepare(self): engine = self.create_engine() return self.runWithConnection(lambda conn: prepare_database(conn, engine, self.config))
def setup_test_homeserver(cleanup_func, name="test", datastore=None, config=None, reactor=None, homeserverToUse=TestHomeServer, **kargs): """ Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied, one is created and given to the homeserver. Args: cleanup_func : The function used to register a cleanup routine for after the test. Calling this method directly is deprecated: you should instead derive from HomeserverTestCase. """ if reactor is None: from twisted.internet import reactor if config is None: config = default_config(name) config.ldap_enabled = False if "clock" not in kargs: kargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: test_db = "synapse_test_%s" % uuid.uuid4().hex config.database_config = { "name": "psycopg2", "args": { "database": test_db, "host": POSTGRES_HOST, "password": POSTGRES_PASSWORD, "user": POSTGRES_USER, "cp_min": 1, "cp_max": 5, }, } else: config.database_config = { "name": "sqlite3", "args": { "database": ":memory:", "cp_min": 1, "cp_max": 1 }, } db_engine = create_engine(config.database_config) # Create the database before we actually try and connect to it, based off # the template database we generate in setupdb() if datastore is None and isinstance(db_engine, PostgresEngine): db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER, host=POSTGRES_HOST, password=POSTGRES_PASSWORD, ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db, )) cur.execute("CREATE DATABASE %s WITH TEMPLATE %s;" % (test_db, POSTGRES_BASE_DB)) cur.close() db_conn.close() # we need to configure the connection pool to run the on_new_connection # function, so that we can test code that uses custom sqlite functions # (like rank). config.database_config["args"]["cp_openfun"] = db_engine.on_new_connection if datastore is None: hs = homeserverToUse(name, config=config, db_config=config.database_config, version_string="Synapse/tests", database_engine=db_engine, tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs) # Prepare the DB on SQLite -- PostgreSQL is a copy of an already up to # date db if not isinstance(db_engine, PostgresEngine): db_conn = hs.get_db_conn() yield prepare_database(db_conn, db_engine, config) db_conn.commit() db_conn.close() else: # We need to do cleanup on PostgreSQL def cleanup(): import psycopg2 # Close all the db pools hs.get_db_pool().close() dropped = False # Drop the test database db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER, host=POSTGRES_HOST, password=POSTGRES_PASSWORD, ) db_conn.autocommit = True cur = db_conn.cursor() # Try a few times to drop the DB. Some things may hold on to the # database for a few more seconds due to flakiness, preventing # us from dropping it when the test is over. If we can't drop # it, warn and move on. for x in range(5): try: cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db, )) db_conn.commit() dropped = True except psycopg2.OperationalError as e: warnings.warn("Couldn't drop old db: " + str(e), category=UserWarning) time.sleep(0.5) cur.close() db_conn.close() if not dropped: warnings.warn("Failed to drop old DB.", category=UserWarning) if not LEAVE_DB: # Register the cleanup hook cleanup_func(cleanup) hs.setup() if homeserverToUse.__name__ == "TestHomeServer": hs.setup_master() else: hs = homeserverToUse(name, db_pool=None, datastore=datastore, config=config, version_string="Synapse/tests", database_engine=db_engine, tls_server_context_factory=Mock(), tls_client_options_factory=Mock(), reactor=reactor, **kargs) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) hs.get_auth_handler().hash = lambda p: hashlib.md5(p.encode('utf8') ).hexdigest() hs.get_auth_handler().validate_hash = ( lambda p, h: hashlib.md5(p.encode('utf8')).hexdigest() == h) fed = kargs.get("resource_for_federation", None) if fed: register_federation_servlets(hs, fed) defer.returnValue(hs)
def setup_test_homeserver(name="test", datastore=None, config=None, **kargs): """Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied a datastore backed by an in-memory sqlite db will be given to the HS. """ if config is None: config = Mock() config.signing_key = [MockKey()] config.event_cache_size = 1 config.enable_registration = True config.macaroon_secret_key = "not even a little secret" config.expire_access_token = False config.server_name = name config.trusted_third_party_id_servers = [] config.room_invite_state_types = [] config.password_providers = [] config.worker_replication_url = "" config.worker_app = None config.email_enable_notifs = False config.block_non_admin_invites = False config.federation_domain_whitelist = None config.federation_rc_reject_limit = 10 config.federation_rc_sleep_limit = 10 config.federation_rc_concurrent = 10 config.filter_timeline_limit = 5000 config.user_directory_search_all_users = False # disable user directory updates, because they get done in the # background, which upsets the test runner. config.update_user_directory = False config.use_frozen_dicts = True config.ldap_enabled = False if "clock" not in kargs: kargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: config.database_config = { "name": "psycopg2", "args": { "database": "synapse_test", "cp_min": 1, "cp_max": 5, }, } else: config.database_config = { "name": "sqlite3", "args": { "database": ":memory:", "cp_min": 1, "cp_max": 1, }, } db_engine = create_engine(config.database_config) # we need to configure the connection pool to run the on_new_connection # function, so that we can test code that uses custom sqlite functions # (like rank). config.database_config["args"]["cp_openfun"] = db_engine.on_new_connection if datastore is None: hs = HomeServer( name, config=config, db_config=config.database_config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), **kargs ) db_conn = hs.get_db_conn() # make sure that the database is empty if isinstance(db_engine, PostgresEngine): cur = db_conn.cursor() cur.execute("SELECT tablename FROM pg_tables where schemaname='public'") rows = cur.fetchall() for r in rows: cur.execute("DROP TABLE %s CASCADE" % r[0]) yield prepare_database(db_conn, db_engine, config) hs.setup() else: hs = HomeServer( name, db_pool=None, datastore=datastore, config=config, version_string="Synapse/tests", database_engine=db_engine, room_list_handler=object(), tls_server_context_factory=Mock(), **kargs ) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) hs.get_auth_handler().hash = lambda p: hashlib.md5(p).hexdigest() hs.get_auth_handler().validate_hash = lambda p, h: hashlib.md5(p).hexdigest() == h fed = kargs.get("resource_for_federation", None) if fed: server.register_servlets( hs, resource=fed, authenticator=server.Authenticator(hs), ratelimiter=FederationRateLimiter( hs.get_clock(), window_size=hs.config.federation_rc_window_size, sleep_limit=hs.config.federation_rc_sleep_limit, sleep_msec=hs.config.federation_rc_sleep_delay, reject_limit=hs.config.federation_rc_reject_limit, concurrent_requests=hs.config.federation_rc_concurrent ), ) defer.returnValue(hs)