Beispiel #1
0
def cli_node_stop(name, system_folders, all_nodes):
    """Stop a running container. """

    client = docker.from_env()
    check_if_docker_deamon_is_running(client)

    running_nodes = client.containers.list(
        filters={"label": f"{APPNAME}-type=node"})

    if not running_nodes:
        warning("No nodes are currently running.")
        return

    running_node_names = [node.name for node in running_nodes]

    if all_nodes:
        for name in running_node_names:
            container = client.containers.get(name)
            container.kill()
            info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
    else:
        if not name:
            name = q.select("Select the node you wish to stop:",
                            choices=running_node_names).ask()
        else:

            post_fix = "system" if system_folders else "user"
            name = f"{APPNAME}-{name}-{post_fix}"

        if name in running_node_names:
            container = client.containers.get(name)
            container.kill()
            info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
        else:
            error(f"{Fore.RED}{name}{Style.RESET_ALL} is not running?")
Beispiel #2
0
def cli_node_list():
    """Lists all nodes in the default configuration directory."""

    client = docker.from_env()
    check_if_docker_deamon_is_running(client)

    running_nodes = client.containers.list(
        filters={"label": f"{APPNAME}-type=node"})

    running_node_names = []
    for node in running_nodes:
        running_node_names.append(node.name)

    header = \
        "\nName"+(21*" ") + \
        "Environments"+(20*" ") + \
        "Status"+(10*" ") + \
        "System/User"

    click.echo(header)
    click.echo("-" * len(header))

    running = Fore.GREEN + "Online" + Style.RESET_ALL
    stopped = Fore.RED + "Offline" + Style.RESET_ALL

    # system folders
    configs, f1 = NodeContext.available_configurations(system_folders=True)
    for config in configs:
        status = running if f"{APPNAME}-{config.name}-system" in \
            running_node_names else stopped
        click.echo(f"{config.name:25}"
                   f"{str(config.available_environments):32}"
                   f"{status:25} System ")

    # user folders
    configs, f2 = NodeContext.available_configurations(system_folders=False)
    for config in configs:
        status = running if f"{APPNAME}-{config.name}-user" in \
            running_node_names else stopped
        click.echo(f"{config.name:25}"
                   f"{str(config.available_environments):32}"
                   f"{status:25} User   ")

    click.echo("-" * 85)
    if len(f1) + len(f2):
        warning(
            f"{Fore.RED}Failed imports: {len(f1)+len(f2)}{Style.RESET_ALL}")
Beispiel #3
0
def cli_node_list():
    """Lists all nodes in the default configuration directories."""

    # FIXME: use package 'table' for this.
    click.echo("\nName" + (21 * " ") + "Environments" + (21 * " ") +
               "System/User")
    click.echo("-" * 70)

    configs, f1 = NodeContext.available_configurations(system_folders=True)
    for config in configs:
        click.echo(f"{config.name:25}{str(config.available_environments):32} "
                   f"System ")

    configs, f2 = NodeContext.available_configurations(system_folders=False)
    for config in configs:
        click.echo(f"{config.name:25}{str(config.available_environments):32} "
                   f"User   ")

    click.echo("-" * 70)
    warning(f"Number of failed imports: "
            f"{Fore.YELLOW}{len(f1)+len(f2)}{Style.RESET_ALL}")
Beispiel #4
0
def cli_server_configuration_list():
    """Print the available configurations."""

    click.echo("\nName"+(21*" ")+"Environments"+(21*" ")+"System/User")
    click.echo("-"*70)

    sys_configs, f1 = ServerContext.available_configurations(
        system_folders=True)
    for config in sys_configs:
        click.echo(f"{config.name:25}{str(config.available_environments):32} System ")

    usr_configs, f2 = ServerContext.available_configurations(system_folders=False)
    for config in usr_configs:
        click.echo(f"{config.name:25}{str(config.available_environments):32} User   ")
    click.echo("-"*70)

    if len(f1)+len(f2):
        warning(
            f"{Fore.YELLOW}Number of failed imports: "
            f"{len(f1)+len(f2)}{Style.RESET_ALL}"
        )
Beispiel #5
0
def cli_node_create_private_key(name, environment, system_folders, upload,
                                organization_name, overwrite):
    """Create and upload a new private key (use with caughtion)"""

    # retrieve context
    name, environment = (name, environment) if name else \
        select_configuration_questionaire("node", system_folders)

    # raise error if config could not be found
    NodeContext.LOGGING_ENABLED = False
    if not NodeContext.config_exists(name, environment, system_folders):
        error(f"The configuration {Fore.RED}{name}{Style.RESET_ALL} with "
              f"environment {Fore.RED}{environment}{Style.RESET_ALL} could "
              f"not be found.")
        exit(1)

    # Create node context
    ctx = NodeContext(name, environment, system_folders)

    # Authenticate with the server to obtain organization name if it wasn't
    # provided
    if organization_name is None:
        client = create_client_and_authenticate(ctx)
        organization_name = client.whoami.organization_name

    # generate new key, and save it
    filename = f"privkey_{organization_name}.pem"
    file_ = ctx.type_data_folder(system_folders) / filename

    if file_.exists():
        warning(f"File '{Fore.CYAN}{file_}{Style.RESET_ALL}' exists!")

        if overwrite:
            warning("'--override' specified, so it will be overwritten ...")

    if file_.exists() and not overwrite:
        error("Could not create private key!")
        warning(f"If you're **sure** you want to create a new key, "
                f"please run this command with the '--overwrite' flag")
        warning("Continuing with existing key instead!")
        private_key = RSACryptor(file_).private_key

    else:
        try:
            info("Generating new private key")
            private_key = RSACryptor.create_new_rsa_key(file_)

        except Exception as e:
            error(f"Could not create new private key '{file_}'!?")
            debug(e)
            info("Bailing out ...")
            exit(1)

        warning(f"Private key written to '{file_}'")
        warning(
            "If you're running multiple nodes, be sure to copy the private "
            "key to the appropriate directories!")

    # create public key
    info("Deriving public key")
    public_key = RSACryptor.create_public_key_bytes(private_key)

    # update config file
    info("Updating configuration")
    ctx.config["encryption"]["private_key"] = str(file_)
    ctx.config_manager.put(environment, ctx.config)
    ctx.config_manager.save(ctx.config_file)

    # upload key to the server
    if upload:
        info("Uploading public key to the server. "
             "This will overwrite any previously existing key!")

        if 'client' not in locals():
            client = create_client_and_authenticate(ctx)

        try:
            client.request(f"/organization/{client.whoami.organization_id}",
                           method="patch",
                           json={"public_key": bytes_to_base64s(public_key)})

        except Exception as e:
            error("Could not upload the public key!")
            debug(e)
            exit(1)

    else:
        warning("Public key not uploaded!")

    info("[Done]")
Beispiel #6
0
def cli_node_start(name, config, environment, system_folders, image, keep,
                   mount_src):
    """Start the node instance.

        If no name or config is specified the default.yaml configuation is
        used. In case the configuration file not excists, a questionaire is
        invoked to create one. Note that in this case it is not possible to
        specify specific environments for the configuration (e.g. test,
        prod, acc).
    """
    info("Starting node...")
    info("Finding Docker deamon")
    docker_client = docker.from_env()
    check_if_docker_deamon_is_running(docker_client)

    if config:
        name = Path(config).stem
        ctx = NodeContext(name, environment, system_folders, config)

    else:
        # in case no name is supplied, ask the user to select one
        if not name:
            name, environment = select_configuration_questionaire(
                "node", system_folders)

        # check that config exists, if not a questionaire will be invoked
        if not NodeContext.config_exists(name, environment, system_folders):
            question = f"Configuration '{name}' using environment"
            question += f" '{environment}' does not exist.\n  Do you want to"
            question += f" create this config now?"

            if q.confirm(question).ask():
                configuration_wizard("node", name, environment, system_folders)

            else:
                error("Config file couldn't be loaded")
                sys.exit(0)

        NodeContext.LOGGING_ENABLED = False
        ctx = NodeContext(name, environment, system_folders)

    # check that this node is not already running
    running_nodes = docker_client.containers.list(
        filters={"label": f"{APPNAME}-type=node"})

    suffix = "system" if system_folders else "user"
    for node in running_nodes:
        if node.name == f"{APPNAME}-{name}-{suffix}":
            error(f"Node {Fore.RED}{name}{Style.RESET_ALL} is already running")
            exit(1)

    # make sure the (host)-task and -log dir exists
    info("Checking that data and log dirs exist")
    ctx.data_dir.mkdir(parents=True, exist_ok=True)
    ctx.log_dir.mkdir(parents=True, exist_ok=True)

    if image is None:
        image = ctx.config.get(
            "image", "harbor.vantage6.ai/infrastructure/node:latest")

    info(f"Pulling latest node image '{image}'")
    try:
        # docker_client.images.pull(image)
        pull_if_newer(image)

    except Exception:
        warning(' ... alas, no dice!')
    else:
        info(" ... success!")

    info("Creating Docker data volume")
    data_volume = docker_client.volumes.create(
        f"{ctx.docker_container_name}-vol")

    info("Creating file & folder mounts")
    # FIXME: should only mount /mnt/database.csv if it is a file!
    # FIXME: should obtain mount points from DockerNodeContext
    mounts = [
        # (target, source)
        ("/mnt/database.csv", str(ctx.databases["default"])),
        ("/mnt/log", str(ctx.log_dir)),
        ("/mnt/data", data_volume.name),
        ("/mnt/config", str(ctx.config_dir)),
        ("/var/run/docker.sock", "/var/run/docker.sock"),
    ]

    if mount_src:
        # If mount_src is a relative path, docker willl consider it a volume.
        mount_src = os.path.abspath(mount_src)
        mounts.append(('/vantage6/vantage6-node', mount_src))

    # FIXME: Code duplication: Node.__init__() (vantage6/node/__init__.py)
    #   uses a lot of the same logic. Suggest moving this to
    #   ctx.get_private_key()
    filename = ctx.config.get("encryption", {}).get("private_key")

    # filename may be set to an empty string
    if not filename:
        filename = 'private_key.pem'

    # Location may be overridden by the environment
    filename = os.environ.get('PRIVATE_KEY', filename)

    # If ctx.get_data_file() receives an absolute path, it is returned as-is
    fullpath = Path(ctx.get_data_file(filename))

    if fullpath:
        if Path(fullpath).exists():
            mounts.append(("/mnt/private_key.pem", str(fullpath)))
        else:
            warning(f"private key file provided {fullpath}, "
                    "but does not exists")

    volumes = {}
    for mount in mounts:
        volumes[mount[1]] = {'bind': mount[0], 'mode': 'rw'}

    # Be careful not to use 'environment' as it would override the function
    # argument ;-).
    env = {
        "DATA_VOLUME_NAME": data_volume.name,
        "DATABASE_URI": "/mnt/database.csv",
        "PRIVATE_KEY": "/mnt/private_key.pem"
    }

    cmd = f'vnode-local start -c /mnt/config/{name}.yaml -n {name} -e '\
          f'{environment} --dockerized'

    info(f"Runing Docker container")
    # debug(f"  with command: '{cmd}'")
    # debug(f"  with mounts: {volumes}")
    # debug(f"  with environment: {env}")

    container = docker_client.containers.run(image,
                                             command=cmd,
                                             volumes=volumes,
                                             detach=True,
                                             labels={
                                                 f"{APPNAME}-type": "node",
                                                 "system": str(system_folders),
                                                 "name": ctx.config_file_name
                                             },
                                             environment=env,
                                             name=ctx.docker_container_name,
                                             auto_remove=not keep,
                                             tty=True)

    info(f"Success! container id = {container}")
Beispiel #7
0
def RPC_adjusted_rate(data, gender, ageclass, incidence, rel_pop,
                      people_at_risk, population, prefacture):
    """Calculates the adjusted rate for the ASR.
       This does this by first creating a pivot of the local node's data using
       the incidence by age class and gender. Then it is multiplied by rel_pop,
       a dataframe created using the function Relative_population. This is all
       divided by the people at risk which is the total population at risk and
       summed across the columns.

    Parameters
    ----------
    data : pd.DataFrame
        Dataframe containing incidence data per ageclass and gender
    gender : pd.DataFrame column
        The different genders
    ageclass : pd.DataFrame column
        The different age classes
    incidence : pd.DataFrame column
        Column containing the incidence (deaths)
    rel_pop : pd.DataFrame
        Standardised population of 100000
    people_at_risk : pd.DataFrame
        Combined dataframe of all the people at risk of catching the disease
        across all nodes
    population: pd.DataFrame column
        Column containing the population per age category and gender at the
        node

    Returns
    -------
    dictionary
        Returns a dictionary object of pd.core.Series containing the adjusted
        rate based on local incidence population and adjusted rate based on
        global population. Depending on how many genders are listed, these will
        be of length len(np.unique(data[gender])).
    """
    # dislcosive check
    n = len(data)
    if n < 10:
        warning('Dataset is too small: len(data) < 10. Exiting.')
        return

    incidence_per_ageclass_and_gender = data.pivot(
        index=[prefacture, gender],
        columns=ageclass,
        values=incidence
    )

    adj_rate_glob = incidence_per_ageclass_and_gender.mul(rel_pop).\
        div(people_at_risk).sum(axis=1)

    incidence_pop = data.pivot(index=gender, columns=ageclass,
                               values=population)

    adj_rate_local = incidence_per_ageclass_and_gender.mul(rel_pop).\
        div(incidence_pop).sum(axis=1)

    return {
        "adj_rate_local": adj_rate_local,
        "adj_rate_glob": adj_rate_glob
    }
Beispiel #8
0
def RPC_preliminairy_results(data, incidence, population, gender, ageclass,
                             prefacture=None):
    """Calculates the crude rate as well as preliminary statistics used for
       calculating the combined crude rate.

    Parameters
    ----------
    data : pd.DataFrame
        Pandas Dataframe containing incidencer per age class and gender
    incidence : pd.DataFrame column
        Column containing the incidence (deaths) at each node
    population : pd.DataFrame column
        Column containing the population at each node
    gender : pd.DataFrame column
        Column containing the different genders at each node
    ageclass : pd.DataFrame column
        Column containing the ageclass's at each node
    prefacture : pd.DataFrame column, optional
        Column containing the prefacture, by default None

    Returns
    -------
    Dictionairy
        Returns a dictionairy object containing the crude rate at the node,
        the total number of incidence cases 'incidence pop' by age class and
        gender, summed population 'total_local_pop' at the node (this is
        indifferent of ageclass and gender) as well as the summed incidence
        (this is also indifferent of ageclass and gender)
    """
    # disclosive check
    n = len(data)
    if n < 10:
        warning('Dataset is too small: len(data) < 10. Exiting')
        return
    if prefacture:
        people_at_risk = data.pivot(
            index=[prefacture, gender],
            columns=ageclass,
            values=population
        ).sum(axis=1)
        incidence_per_prefacture = data.pivot(
            index=[prefacture, gender],
            columns=ageclass,
            values=incidence
        ).sum(axis=1)
        crd_rate = (incidence_per_prefacture.div(people_at_risk))*100000
    else:
        people_at_risk = data.pivot_table(
            index=gender,
            columns=ageclass,
            values=population
        ).sum(axis=1)
        incidence_per_gender_age = data.pivot_table(
            index=gender,
            columns=ageclass,
            values=incidence
        ).sum(axis=1)
        crd_rate = (incidence_per_gender_age.div(people_at_risk))*100000
    incidence_pop = data.pivot(index=gender, columns=ageclass,
                               values=population)
    total_local_pop = incidence_pop.sum(axis=1)
    total_local_inc = data.pivot(index=gender, columns=ageclass,
                                 values=incidence).sum(axis=1)
    return {
        "crude_rate": crd_rate,
        "incidence_population": incidence_pop,
        "total_local_population": total_local_pop,
        "total_local_incidence": total_local_inc
    }
Beispiel #9
0
def cli_server_import(ctx, file_, drop_all, image, keep):
    """ Import organizations/collaborations/users and tasks.

        Especially usefull for testing purposes.
    """
    info("Starting server...")
    info("Finding Docker daemon.")
    docker_client = docker.from_env()
    # will print an error if not
    check_if_docker_deamon_is_running(docker_client)

    # pull lastest Docker image
    if image is None:
        image = ctx.config.get(
            "image", "harbor.vantage6.ai/infrastructure/server:latest")
    info(f"Pulling latest server image '{image}'.")
    try:
        docker_client.images.pull(image)
    except Exception:
        warning("... alas, no dice!")
    else:
        info(" ... succes!")

    info("Creating mounts")
    mounts = [
        docker.types.Mount("/mnt/config.yaml",
                           str(ctx.config_file),
                           type="bind"),
        docker.types.Mount("/mnt/import.yaml", str(file_), type="bind")
    ]

    # FIXME: code duplication with cli_server_start()
    # try to mount database
    uri = ctx.config['uri']
    url = make_url(uri)
    environment_vars = None

    # If host is None, we're dealing with a file-based DB, like SQLite
    if (url.host is None):
        db_path = url.database

        if not os.path.isabs(db_path):
            # We're dealing with a relative path here -> make it absolute
            db_path = ctx.data_dir / url.database

        basename = os.path.basename(db_path)
        dirname = os.path.dirname(db_path)
        os.makedirs(dirname, exist_ok=True)

        # we're mounting the entire folder that contains the database
        mounts.append(
            docker.types.Mount("/mnt/database/", dirname, type="bind"))

        environment_vars = {
            "VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}"
        }

    else:
        warning(f"Database could not be transfered, make sure {url.host} "
                "is reachable from the Docker container")
        info("Consider using the docker-compose method to start a server")

    drop_all_ = "--drop-all" if drop_all else ""
    cmd = f'vserver-local import -c /mnt/config.yaml -e {ctx.environment} ' \
          f'{drop_all_} /mnt/import.yaml'

    info(cmd)

    info("Run Docker container")
    container = docker_client.containers.run(image,
                                             command=cmd,
                                             mounts=mounts,
                                             detach=True,
                                             labels={
                                                 f"{APPNAME}-type": "server",
                                                 "name": ctx.config_file_name
                                             },
                                             environment=environment_vars,
                                             auto_remove=not keep,
                                             tty=True)
    logs = container.logs(stream=True, stdout=True)
    Thread(target=print_log_worker, args=(logs, ), daemon=False).start()

    info(f"Succes! container id = {container.id}")
Beispiel #10
0
def cli_server_start(ctx, ip, port, debug, image, keep):
    """Start the server."""

    info("Starting server...")
    info("Finding Docker daemon.")
    docker_client = docker.from_env()
    # will print an error if not
    check_if_docker_deamon_is_running(docker_client)

    # check that this server is not already running
    running_servers = docker_client.containers.list(
        filters={"label": f"{APPNAME}-type=server"})
    for server in running_servers:
        if server.name == f"{APPNAME}-{ctx.name}-{ctx.scope}-server":
            error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} "
                  "is already running")
            exit(1)

    # pull the server docker image
    if image is None:
        image = ctx.config.get(
            "image", "harbor.vantage6.ai/infrastructure/server:latest")
    info(f"Pulling latest server image '{image}'.")
    try:
        pull_if_newer(image)
        # docker_client.images.pull(image)
    except Exception:
        warning("... alas, no dice!")
    else:
        info(" ... succes!")

    info("Creating mounts")
    mounts = [
        docker.types.Mount("/mnt/config.yaml",
                           str(ctx.config_file),
                           type="bind")
    ]

    # FIXME: code duplication with cli_server_import()
    # try to mount database
    uri = ctx.config['uri']
    url = make_url(uri)
    environment_vars = None

    # If host is None, we're dealing with a file-based DB, like SQLite
    if (url.host is None):
        db_path = url.database

        if not os.path.isabs(db_path):
            # We're dealing with a relative path here -> make it absolute
            db_path = ctx.data_dir / url.database

        basename = os.path.basename(db_path)
        dirname = os.path.dirname(db_path)
        os.makedirs(dirname, exist_ok=True)

        # we're mounting the entire folder that contains the database
        mounts.append(
            docker.types.Mount("/mnt/database/", dirname, type="bind"))

        environment_vars = {
            "VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}"
        }

    else:
        warning(f"Database could not be transfered, make sure {url.host} "
                "is reachable from the Docker container")
        info("Consider using the docker-compose method to start a server")

    ip_ = f"--ip {ip}" if ip else ""
    port_ = f"--port {port}" if port else ""
    cmd = f'vserver-local start -c /mnt/config.yaml -e {ctx.environment} ' \
          f'{ip_} {port_}'
    info(cmd)

    info("Run Docker container")
    port_ = str(port or ctx.config["port"] or 5000)
    container = docker_client.containers.run(
        image,
        command=cmd,
        mounts=mounts,
        detach=True,
        labels={
            f"{APPNAME}-type": "server",
            "name": ctx.config_file_name
        },
        environment=environment_vars,
        ports={f"{port_}/tcp": ("127.0.0.1", port_)},
        name=ctx.docker_container_name,
        auto_remove=not keep,
        tty=True)

    info(f"Succes! container id = {container}")