Source code for swh.provenance.grpc_server
# Copyright (C) 2021-2024  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import aiohttp.test_utils
import aiohttp.web
logger = logging.getLogger(__name__)
[docs]
class ExecutableNotFound(EnvironmentError):
    pass 
[docs]
def default_rust_executable_dir(config):
    debug_mode = config.get("debug", "pytest" in sys.modules)
    # look for a target/ directory in the sources root directory
    profile = "debug" if debug_mode else "release"
    # in editable installs, __file__ is a symlink to the original file in
    # the source directory, which is where in the end the rust sources and
    # executable are. So resolve the symlink before looking for the target/
    # directory relative to the actual python file.
    path = Path(__file__).resolve()
    path = path.parent.parent.parent / "target" / profile
    return path 
[docs]
def build_rust_grpc_server_cmdline(**config):
    logger.debug("Checking configuration and populating default values")
    port = config.pop("port", None)
    if port is None:
        port = aiohttp.test_utils.unused_port()
        logger.debug("Port not configured, using random port %s", port)
    rust_executable_dir = config.get(
        "rust_executable_dir"
    ) or default_rust_executable_dir(config)
    print(rust_executable_dir)
    grpc_path = str(rust_executable_dir) + "/swh-provenance-grpc-serve"
    if not os.path.isfile(grpc_path):
        grpc_path = shutil.which("swh-provenance-grpc-serve")
    if not grpc_path or not os.path.isfile(grpc_path):
        raise ExecutableNotFound("swh-provenance-grpc-serve executable not found")
    cmd = [str(grpc_path)]
    logger.debug("Configuration: %r", config)
    cmd.extend(["--bind", f"[::]:{port}"])
    cmd.extend(["--database", str(config["db"])])
    cmd.extend(["--graph", str(config["graph"])])
    if "indexes" in config:
        cmd.extend(["--indexes", str(config["indexes"])])
    if config.get("graph_format"):
        cmd.extend(["--graph-format", config["graph_format"]])
    print(f"Started GRPC using dataset from {grpc_path}")
    return cmd, port 
[docs]
def spawn_rust_grpc_server(**config):
    cmd, port = build_rust_grpc_server_cmdline(**config)
    print(cmd)
    # XXX: shlex.join() is in 3.8
    # logger.info("Starting gRPC server: %s", shlex.join(cmd))
    logger.info("Starting gRPC server: %s", " ".join(shlex.quote(x) for x in cmd))
    env = dict(os.environ)
    if config.get("debug", False):
        env.setdefault(
            "RUST_LOG", "debug,h2=info,tonic=info"
        )  # h2 and tonic are very verbose at DEBUG level
    if "statsd_host" in config:
        env["STATSD_HOST"] = config["statsd_host"]
    if "statsd_port" in config:
        env["STATSD_PORT"] = str(config["statsd_port"])
    server = subprocess.Popen(cmd, env=env)
    return server, port 
[docs]
def stop_grpc_server(server: subprocess.Popen, timeout: int = 15):
    server.terminate()
    try:
        server.wait(timeout=timeout)
    except subprocess.TimeoutExpired:
        logger.warning("Server did not terminate, sending kill signal...")
        server.kill()