async def _backend_factory(populated=True, config={}, event_bus=None): config = BackendConfig( **{ "administration_token": "s3cr3t", "db_drop_deleted_data": False, "db_min_connections": 1, "db_max_connections": 5, "debug": False, "db_url": backend_store, "blockstore_config": blockstore, **config, }) if not event_bus: event_bus = event_bus_factory() # TODO: backend connection to postgresql will timeout if we use a trio # mock clock with autothreshold. We should detect this and do something here... async with backend_app_factory(config, event_bus=event_bus) as backend: if populated: with freeze_time("2000-01-01"): binder = backend_data_binder_factory(backend) await binder.bind_organization(coolorg, alice) await binder.bind_organization( expiredorg, expiredalice, expiration_date=pendulum.now()) await binder.bind_organization(otherorg, otheralice) await binder.bind_device(alice2) await binder.bind_device(adam) await binder.bind_device(bob) yield backend
async def _backend_factory(populated=True, config={}, event_bus=None): ssl_context = fixtures_customization.get("backend_over_ssl", False) nonlocal backend_store, blockstore if fixtures_customization.get("backend_force_mocked"): backend_store = "MOCKED" assert fixtures_customization.get("blockstore_mode", "NO_RAID") == "NO_RAID" blockstore = MockedBlockStoreConfig() config = BackendConfig( **{ "administration_token": "s3cr3t", "db_min_connections": 1, "db_max_connections": 5, "debug": False, "db_url": backend_store, "blockstore_config": blockstore, "email_config": None, "backend_addr": None, "forward_proto_enforce_https": None, "ssl_context": ssl_context if ssl_context else False, "organization_bootstrap_webhook_url": None, "organization_spontaneous_bootstrap": False, **config, }) if not event_bus: event_bus = event_bus_factory() async with backend_app_factory(config, event_bus=event_bus) as backend: if populated: with freeze_time("2000-01-01"): binder = backend_data_binder_factory(backend) await binder.bind_organization( coolorg, alice, initial_user_manifest=fixtures_customization.get( "alice_initial_remote_user_manifest", "v1"), ) await binder.bind_organization(expiredorg, expiredorgalice) await backend.organization.update( expiredorg.organization_id, is_expired=True) await binder.bind_organization(otherorg, otheralice) await binder.bind_device(alice2, certifier=alice) await binder.bind_device( adam, certifier=alice2, initial_user_manifest=fixtures_customization.get( "adam_initial_remote_user_manifest", "v1"), ) await binder.bind_device( bob, certifier=adam, initial_user_manifest=fixtures_customization.get( "bob_initial_remote_user_manifest", "v1"), ) yield backend
async def test_retry_policy_allow_retry(postgresql_url, unused_tcp_port, asyncio_loop): host = "localhost" port = unused_tcp_port app_config = BackendConfig( administration_token="s3cr3t", db_min_connections=1, db_max_connections=5, debug=False, blockstore_config=PostgreSQLBlockStoreConfig(), email_config=None, backend_addr=None, forward_proto_enforce_https=None, ssl_context=False, spontaneous_organization_bootstrap=False, organization_bootstrap_webhook_url=None, db_url=postgresql_url, ) # Allow to retry once retry_policy = RetryPolicy(maximum_attempts=1, pause_before_retry=0) async with trio.open_nursery() as nursery: # Run backend in the background nursery.start_soon(lambda: _run_backend(host, port, ssl_context=False, retry_policy=retry_policy, app_config=app_config)) # Connect to PostgreSQL database async with triopg.connect(postgresql_url) as conn: # Test for 10 cycles pid = None for _ in range(10): # Wait for the backend to be connected new_pid, = await wait_for_listeners(conn) # Make sure a new connection has been created assert new_pid != pid pid = new_pid # Terminate the backend listener connection value, = await conn.fetchrow("SELECT pg_terminate_backend($1)", pid) assert value # Wait for the listener to terminate await wait_for_listeners(conn, to_terminate=True) # Cancel the backend nursery nursery.cancel_scope.cancel()
async def test_retry_policy_no_retry(postgresql_url, unused_tcp_port, asyncio_loop): host = "localhost" port = unused_tcp_port app_config = BackendConfig( administration_token="s3cr3t", db_min_connections=1, db_max_connections=5, debug=False, blockstore_config=PostgreSQLBlockStoreConfig(), email_config=None, backend_addr=None, forward_proto_enforce_https=None, ssl_context=False, spontaneous_organization_bootstrap=False, organization_bootstrap_webhook_url=None, db_url=postgresql_url, ) # No retry retry_policy = RetryPolicy(maximum_attempts=0, pause_before_retry=0) # Expect a connection error with pytest.raises(ConnectionError): async with trio.open_nursery() as nursery: # Run backend in the background nursery.start_soon(lambda: _run_backend(host, port, ssl_context=False, retry_policy=retry_policy, app_config=app_config)) # Connect to PostgreSQL database async with triopg.connect(postgresql_url) as conn: # Wait for the backend to be connected pid, = await wait_for_listeners(conn) # Terminate the backend listener connection value, = await conn.fetchrow("SELECT pg_terminate_backend($1)", pid) assert value # Wait to get cancelled by the connection error `_run_backend` with trio.fail_after(3): await trio.sleep_forever()
async def _backend_factory(populated=True, config={}, event_bus=None): ssl_context = fixtures_customization.get("backend_over_ssl", False) config = BackendConfig( **{ "administration_token": "s3cr3t", "db_min_connections": 1, "db_max_connections": 5, "debug": False, "db_url": backend_store, "blockstore_config": blockstore, "email_config": None, "backend_addr": None, "forward_proto_enforce_https": None, "ssl_context": ssl_context if ssl_context else False, "spontaneous_organization_bootstrap": False, "organization_bootstrap_webhook_url": None, **config, }) if not event_bus: event_bus = event_bus_factory() # TODO: backend connection to postgresql will timeout if we use a trio # mock clock with autothreshold. We should detect this and do something here... async with backend_app_factory(config, event_bus=event_bus) as backend: if populated: with freeze_time("2000-01-01"): binder = backend_data_binder_factory(backend) await binder.bind_organization(coolorg, alice) await binder.bind_organization( expiredorg, expiredorgalice, expiration_date=pendulum.now()) await binder.bind_organization(otherorg, otheralice) await binder.bind_device(alice2, certifier=alice) await binder.bind_device(adam, certifier=alice2) await binder.bind_device(bob, certifier=adam) yield backend
def run_cmd( host, port, db, db_drop_deleted_data, db_min_connections, db_max_connections, blockstore, administration_token, ssl_keyfile, ssl_certfile, log_level, log_format, log_file, log_filter, sentry_url, debug, dev, ): configure_logging(log_level, log_format, log_file, log_filter) if sentry_url: configure_sentry_logging(sentry_url) with cli_exception_handler(debug): config = BackendConfig( administration_token=administration_token, db_url=db, db_drop_deleted_data=db_drop_deleted_data, db_min_connections=db_min_connections, db_max_connections=db_max_connections, blockstore_config=blockstore, debug=debug, ) if ssl_certfile or ssl_keyfile: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) if ssl_certfile: ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) else: ssl_context.load_default_certs() else: ssl_context = None async def _run_backend(): async with backend_app_factory(config=config) as backend: async def _serve_client(stream): if ssl_context: stream = trio.SSLStream(stream, ssl_context, server_side=True) try: await backend.handle_client(stream) except Exception: # If we are here, something unexpected happened... logger.exception("Unexpected crash") await stream.aclose() await trio.serve_tcp(_serve_client, port, host=host) click.echo( f"Starting Parsec Backend on {host}:{port} (db={config.db_type}, " f"blockstore={config.blockstore_config.type})" ) try: trio_run(_run_backend, use_asyncio=True) except KeyboardInterrupt: click.echo("bye ;-)")
def run_cmd( host, port, db, db_min_connections, db_max_connections, db_first_tries_number, db_first_tries_sleep, blockstore, administration_token, spontaneous_organization_bootstrap, organization_bootstrap_webhook, backend_addr, email_host, email_port, email_host_user, email_host_password, email_use_ssl, email_use_tls, email_sender, forward_proto_enforce_https, ssl_keyfile, ssl_certfile, log_level, log_format, log_file, log_filter, sentry_url, debug, dev, ): # Start a local backend configure_logging(log_level, log_format, log_file, log_filter) if sentry_url: configure_sentry_logging(sentry_url) with cli_exception_handler(debug): if ssl_certfile or ssl_keyfile: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) if ssl_certfile: ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) else: ssl_context.load_default_certs() else: ssl_context = None if email_host == "MOCKED": tmpdir = tempfile.mkdtemp(prefix="tmp-email-folder-") if email_sender: email_config = MockedEmailConfig(sender=email_sender, tmpdir=tmpdir) else: email_config = MockedEmailConfig(sender=DEFAULT_EMAIL_SENDER, tmpdir=tmpdir) else: if not email_sender: raise ValueError( "--email-sender is required when --email-host is provided") email_config = SmtpEmailConfig( host=email_host, port=email_port, host_user=email_host_user, host_password=email_host_password, use_ssl=email_use_ssl, use_tls=email_use_tls, sender=email_sender, ) config = BackendConfig( administration_token=administration_token, db_url=db, db_min_connections=db_min_connections, db_max_connections=db_max_connections, db_first_tries_number=db_first_tries_number, db_first_tries_sleep=db_first_tries_sleep, spontaneous_organization_bootstrap= spontaneous_organization_bootstrap, organization_bootstrap_webhook_url=organization_bootstrap_webhook, blockstore_config=blockstore, email_config=email_config, ssl_context=True if ssl_context else False, forward_proto_enforce_https=forward_proto_enforce_https, backend_addr=backend_addr, debug=debug, ) async def _run_backend(): async with backend_app_factory(config=config) as backend: async def _serve_client(stream): if ssl_context: stream = trio.SSLStream(stream, ssl_context, server_side=True) try: await backend.handle_client(stream) except Exception: # If we are here, something unexpected happened... logger.exception("Unexpected crash") await stream.aclose() await trio.serve_tcp(_serve_client, port, host=host) click.echo(f"Starting Parsec Backend on {host}:{port}" f" (db={config.db_type}" f" blockstore={config.blockstore_config.type}" f" backend_addr={config.backend_addr}" f" email_config={str(email_config)})") try: trio_run(_run_backend, use_asyncio=True) except KeyboardInterrupt: click.echo("bye ;-)")
def run_cmd( host: str, port: int, db: str, db_min_connections: int, db_max_connections: int, maximum_database_connection_attempts: int, pause_before_retry_database_connection: float, blockstore: BaseBlockStoreConfig, administration_token: str, spontaneous_organization_bootstrap: bool, organization_bootstrap_webhook: str, organization_initial_active_users_limit: int, organization_initial_user_profile_outsider_allowed: bool, backend_addr: BackendAddr, email_host: str, email_port: int, email_host_user: Optional[str], email_host_password: Optional[str], email_use_ssl: bool, email_use_tls: bool, email_sender: str, forward_proto_enforce_https: Optional[Tuple[bytes, bytes]], ssl_keyfile: Optional[Path], ssl_certfile: Optional[Path], log_level: str, log_format: str, log_file: Optional[str], sentry_dsn: Optional[str], sentry_environment: str, debug: bool, dev: bool, ) -> None: # Start a local backend with cli_exception_handler(debug): ssl_context: Optional[ssl.SSLContext] if ssl_certfile or ssl_keyfile: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) if ssl_certfile: ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) else: ssl_context.load_default_certs() else: ssl_context = None email_config: EmailConfig if email_host == "MOCKED": tmpdir = tempfile.mkdtemp(prefix="tmp-email-folder-") if email_sender: email_config = MockedEmailConfig(sender=email_sender, tmpdir=tmpdir) else: email_config = MockedEmailConfig(sender=DEFAULT_EMAIL_SENDER, tmpdir=tmpdir) else: if not email_sender: raise ValueError( "--email-sender is required when --email-host is provided") email_config = SmtpEmailConfig( host=email_host, port=email_port, host_user=email_host_user, host_password=email_host_password, use_ssl=email_use_ssl, use_tls=email_use_tls, sender=email_sender, ) app_config = BackendConfig( administration_token=administration_token, db_url=db, db_min_connections=db_min_connections, db_max_connections=db_max_connections, blockstore_config=blockstore, email_config=email_config, ssl_context=True if ssl_context else False, forward_proto_enforce_https=forward_proto_enforce_https, backend_addr=backend_addr, debug=debug, organization_bootstrap_webhook_url=organization_bootstrap_webhook, organization_spontaneous_bootstrap= spontaneous_organization_bootstrap, organization_initial_active_users_limit= organization_initial_active_users_limit, organization_initial_user_profile_outsider_allowed= organization_initial_user_profile_outsider_allowed, ) click.echo(f"Starting Parsec Backend on {host}:{port}" f" (db={app_config.db_type}" f" blockstore={app_config.blockstore_config.type}" f" email={email_config.type}" f" telemetry={'on' if sentry_dsn else 'off'}" f" backend_addr={app_config.backend_addr}" ")") try: retry_policy = RetryPolicy(maximum_database_connection_attempts, pause_before_retry_database_connection) trio_run(_run_backend, host, port, ssl_context, retry_policy, app_config, use_asyncio=True) except KeyboardInterrupt: click.echo("bye ;-)")
def run_cmd( host, port, db, db_min_connections, db_max_connections, maximum_database_connection_attempts, pause_before_retry_database_connection, blockstore, administration_token, spontaneous_organization_bootstrap, organization_bootstrap_webhook, backend_addr, email_host, email_port, email_host_user, email_host_password, email_use_ssl, email_use_tls, email_sender, forward_proto_enforce_https, ssl_keyfile, ssl_certfile, log_level, log_format, log_file, sentry_url, debug, dev, ): # Start a local backend configure_logging(log_level=log_level, log_format=log_format, log_file=log_file) if sentry_url: configure_sentry_logging(sentry_url) with cli_exception_handler(debug): if ssl_certfile or ssl_keyfile: ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) if ssl_certfile: ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) else: ssl_context.load_default_certs() else: ssl_context = None if email_host == "MOCKED": tmpdir = tempfile.mkdtemp(prefix="tmp-email-folder-") if email_sender: email_config = MockedEmailConfig(sender=email_sender, tmpdir=tmpdir) else: email_config = MockedEmailConfig(sender=DEFAULT_EMAIL_SENDER, tmpdir=tmpdir) else: if not email_sender: raise ValueError( "--email-sender is required when --email-host is provided") email_config = SmtpEmailConfig( host=email_host, port=email_port, host_user=email_host_user, host_password=email_host_password, use_ssl=email_use_ssl, use_tls=email_use_tls, sender=email_sender, ) app_config = BackendConfig( administration_token=administration_token, db_url=db, db_min_connections=db_min_connections, db_max_connections=db_max_connections, spontaneous_organization_bootstrap= spontaneous_organization_bootstrap, organization_bootstrap_webhook_url=organization_bootstrap_webhook, blockstore_config=blockstore, email_config=email_config, ssl_context=True if ssl_context else False, forward_proto_enforce_https=forward_proto_enforce_https, backend_addr=backend_addr, debug=debug, ) click.echo(f"Starting Parsec Backend on {host}:{port}" f" (db={app_config.db_type}" f" blockstore={app_config.blockstore_config.type}" f" backend_addr={app_config.backend_addr}" f" email_config={str(email_config)})") try: retry_policy = RetryPolicy(maximum_database_connection_attempts, pause_before_retry_database_connection) trio_run(_run_backend, host, port, ssl_context, retry_policy, app_config, use_asyncio=True) except KeyboardInterrupt: click.echo("bye ;-)")