Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): pass docker run args to session start #3487

Merged
merged 2 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ blog
BMP
bugfix
Calamus
cgroup
chartpress
Chartpress
checksum
Expand All @@ -51,16 +52,17 @@ CWL
datadir
dataset
datasets
datetimes
dataverse
Dataverse
datetimes
deployer
deserialization
deserialize
Deserialize
deserialized
Deserialized
deserializing
dev
discoverable
Dockerfile
dockerfiles
Expand All @@ -79,10 +81,6 @@ filesystem
FilterFlights
findable
Fortran
GitLab
GitPython
GraphQL
graphviz
gapped
git-lfs
gitattributes
Expand All @@ -91,8 +89,12 @@ github
gitignore
gitignored
gitkeep
GitLab
gitlab
gitlabClientSecret
GitPython
GraphQL
graphviz
hexsha
Homebrew
hostname
Expand Down Expand Up @@ -138,6 +140,7 @@ Matlab
md5
mergetool
metadata
metavar
microservices
middleware
migrationscheck
Expand All @@ -162,11 +165,12 @@ OpenID
openssl
papermill
param
params
parameterizable
parametrization
parametrize
parametrized
params
PIDs
pipenv
PNG
Postgresql
Expand All @@ -193,8 +197,8 @@ refactored
Renga
renku
Renku
renkulab
renku-mls
renkulab
renv
repo
reproducibility
Expand All @@ -218,6 +222,7 @@ scala
serializer
sha
shacl
shm
Slurm
Snyk
SPARQL
Expand Down Expand Up @@ -252,6 +257,7 @@ subsubcommands
sudo
supertype
supertypes
swappiness
symlink
symlinks
templated
Expand All @@ -260,11 +266,11 @@ Tensorflow
timestamp
tinkerpop
toolchain
TTY
tutorialLink
txt
typesystem
Ubuntu
Unmount
ui
Unescape
unhandled
Expand All @@ -274,12 +280,13 @@ Unlink
unlinking
unmapped
unmerged
Unmount
unpushed
untracked
untracked
updatable
url
uri
url
urls
username
validator
Expand Down
1 change: 1 addition & 0 deletions renku/core/dataset/providers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class ProviderParameter(NamedTuple):
is_flag: bool = False
multiple: bool = False
type: Optional[Type] = None
metavar: Optional[str] = None


class ProviderDataset(Dataset):
Expand Down
135 changes: 124 additions & 11 deletions renku/core/session/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import webbrowser
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, cast
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Union, cast
from uuid import uuid4

import docker
Expand Down Expand Up @@ -126,6 +126,97 @@ def get_start_parameters(self) -> List["ProviderParameter"]:
return [
ProviderParameter("port", help="Local port to use (random if not specified).", type=int),
ProviderParameter("force-build", help="Always build image and don't check if it exists.", is_flag=True),
ProviderParameter(
"blkio-weight", help="Block IO (relative weight), between 10 and 1000, or 0 to disable.", type=int
),
ProviderParameter("cap-add", help="Add Linux capabilities.", multiple=True),
ProviderParameter("cap-drop", help="Drop Linux capabilities.", multiple=True),
ProviderParameter("cgroup-parent", help="Override the default parent cgroup.", type=str),
ProviderParameter("cpu-count", help="Number of usable CPUs.", type=int),
ProviderParameter("cpu-percent", help="Usable percentage of the available CPUs.", type=int),
ProviderParameter("cpu-period", help="The length of a CPU period in microseconds.", type=int),
ProviderParameter(
"cpu-quota", help="Microseconds of CPU time that the container can get in a CPU period.", type=int
),
ProviderParameter("cpu-rt-period", help="Limit CPU real-time period in microseconds.", type=int),
ProviderParameter("cpu-rt-runtime", help="Limit CPU real-time runtime in microseconds.", type=int),
ProviderParameter("cpu-shares", help="CPU shares (relative weight).", type=int),
ProviderParameter("cpuset-cpus", help="CPUs in which to allow execution ('0-3', '0,1').", type=str),
ProviderParameter(
"cpuset-mems", help="Memory nodes (MEMs) in which to allow execution ('0-3', '0,1').", type=str
),
ProviderParameter(
"device-cgroup-rules",
help="A list of cgroup rules to apply to the container.",
multiple=True,
flags=["device-cgroup-rule"],
),
ProviderParameter("devices", help="Expose host devices to the container.", multiple=True, flags=["device"]),
ProviderParameter("dns", help="Set custom DNS servers.", multiple=True),
ProviderParameter(
"dns-opt",
help="Additional options to be added to the container's ``resolv.conf`` file.",
type=str,
flags=["dns-opt", "dns-option"],
),
ProviderParameter("dns-search", help="DNS search domains.", multiple=True),
ProviderParameter("domainname", help="Container NIS domain name.", type=str),
ProviderParameter("entrypoint", help="The entrypoint for the container.", type=str),
ProviderParameter(
"environment",
help="Environment variables to set inside the container, in the format 'VAR=VAL'",
multiple=True,
flags=["env"],
),
ProviderParameter(
"group-add",
help="List of additional group names and/or IDs that the container process will run as.",
multiple=True,
),
ProviderParameter("hostname", help="Optional hostname for the container.", type=str),
ProviderParameter(
"init", help="Run an init inside the container that forwards signals and reaps processes", is_flag=True
),
ProviderParameter("isolation", help="Isolation technology to use.", type=str),
ProviderParameter("kernel-memory", help="Kernel memory limit (bytes).", type=int, metavar="<bytes>"),
ProviderParameter("mac-address", help="MAC address to assign to the container.", type=str),
ProviderParameter("mem-reservation", help="Memory soft limit.", type=int, flags=["memory-reservation"]),
ProviderParameter(
"mem-swappiness",
help="Tune container memory swappiness (0 to 100).",
type=int,
flags=["memory-swappiness"],
),
ProviderParameter("memswap-limit", help="Swap limit equal to memory plus swap.", flags=["memory-swap"]),
ProviderParameter("name", help="The name for this container.", type=str),
ProviderParameter("network", help="Connect a container to a network.", type=str),
ProviderParameter("oom-kill-disable", help="Disable OOM Killer.", is_flag=True),
ProviderParameter("oom-score-adj", help="Tune host's OOM preferences (-1000 to 1000).", type=int),
ProviderParameter("pids-limit", help="Tune a container's PIDs limit.", type=int),
ProviderParameter("platform", help="Set platform if server is multi-platform capable.", type=str),
ProviderParameter("privileged", help="Give extended privileges to this container.", is_flag=True),
ProviderParameter(
"publish-all-ports", help="Publish all ports to the host.", is_flag=True, flags=["publish-all"]
),
ProviderParameter("read-only", help="Mount the container's root filesystem as read-only", is_flag=True),
ProviderParameter("remove", help="Automatically remove the container when it exits.", flags=["rm"]),
ProviderParameter("runtime", help="Runtime to use with this container.", type=str),
ProviderParameter("security-opt", help="Security Options.", multiple=True),
ProviderParameter("shm-size", help="Size of /dev/shm (bytes).", type=int, metavar="<bytes>"),
ProviderParameter(
"stdin-open", help="Keep STDIN open even if not attached.", is_flag=True, flags=["interactive"]
),
ProviderParameter("stop-signal", help="Signal to stop the container.", type=str),
ProviderParameter("tty", help="Allocate a pseudo-TTY.", is_flag=True),
ProviderParameter("user", help="Username or UID", type=str),
ProviderParameter("volume-driver", help="The name of a volume driver/plugin.", type=str),
ProviderParameter(
"volumes",
help="A list of volume mounts (e.g. '/host/path/:/mount/path/in/container')",
multiple=True,
flags=["volume"],
),
ProviderParameter("volumes-from", help="Mount volumes from the specified container(s)", multiple=True),
]

def get_open_parameters(self) -> List["ProviderParameter"]:
Expand Down Expand Up @@ -162,7 +253,7 @@ def session_start(
cpu_request: Optional[float] = None,
mem_request: Optional[str] = None,
disk_request: Optional[str] = None,
gpu_request: Optional[str] = None,
gpu_request: Optional[Union[str, int]] = None,
**kwargs,
) -> Tuple[str, str]:
"""Creates an interactive session.
Expand All @@ -173,6 +264,8 @@ def session_start(
show_non_standard_user_warning = True

def session_start_helper(consider_disk_request: bool):
nonlocal gpu_request

try:
docker_is_running = self.docker_client().ping()
if not docker_is_running:
Expand Down Expand Up @@ -201,8 +294,15 @@ def session_start_helper(consider_disk_request: bool):
docker.types.DeviceRequest(count=-1, capabilities=[["compute", "utility"]])
]
else:
if not isinstance(gpu_request, int):
try:
gpu_request = int(gpu_request)
except ValueError:
raise errors.ParameterError(
f"Invalid value for 'gpu': '{gpu_request}'. Valid values are integers or 'all'"
)
resource_requests["device_requests"] = [
docker.types.DeviceRequest(count=[gpu_request], capabilities=[["compute", "utility"]])
docker.types.DeviceRequest(count=gpu_request, capabilities=[["compute", "utility"]])
]

# NOTE: set git user
Expand All @@ -214,15 +314,27 @@ def session_start_helper(consider_disk_request: bool):

work_dir = Path(working_dir) / "work" / project_name.split("/")[-1]

volumes = [f"{str(project_context.path.resolve())}:{work_dir}"]
volumes = kwargs.pop("volumes", [])
volumes = list(volumes)
volumes.append(f"{str(project_context.path.resolve())}:{work_dir}")

environment = {}
passed_env_vars = kwargs.pop("environment", [])
for env_var in passed_env_vars:
var, _, value = env_var.partition("=")
if not var:
raise errors.ParameterError(f"Invalid environment variable: '{env_var}'")
environment[var] = value

user = project_context.repository.get_user()
environment = {
"GIT_AUTHOR_NAME": user.name,
"GIT_AUTHOR_EMAIL": user.email,
"GIT_COMMITTER_EMAIL": user.email,
"EMAIL": user.email,
}
environment.update(
{
"GIT_AUTHOR_NAME": user.name,
"GIT_AUTHOR_EMAIL": user.email,
"GIT_COMMITTER_EMAIL": user.email,
"EMAIL": user.email,
}
)

additional_options: Dict[str, Any] = {}

Expand All @@ -239,7 +351,7 @@ def session_start_helper(consider_disk_request: bool):
)
show_non_standard_user_warning = False

additional_options["user"] = "root"
additional_options["user"] = kwargs.pop("user", "root")
environment["NB_UID"] = str(os.getuid())
environment["CHOWN_HOME"] = "yes"
environment["CHOWN_HOME_OPTS"] = "-R"
Expand Down Expand Up @@ -267,6 +379,7 @@ def session_start_helper(consider_disk_request: bool):
working_dir=str(work_dir),
**resource_requests,
**additional_options,
**kwargs,
)

if not container.ports:
Expand Down
22 changes: 17 additions & 5 deletions renku/ui/cli/session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#
# Copyright 2018-2023 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Copyright Swiss Data Science Center (SDSC). A partnership between
# École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -59,6 +58,11 @@
Finally, it prompts the user to build the image locally if no image is found. You
can force the image to always be built by using the ``--force-build`` flag.

This command accepts a subset of arguments of the ``docker run`` command. See
its help for the list of supported arguments: ``renku session start --help``.
Accepted values are the same as the ``docker run`` command unless stated
otherwise.

Renkulab provider
~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -109,7 +113,8 @@
$ renku session start -p renkulab --ssh
Your system is not set up for SSH connections to Renkulab. Would you like to set it up? [y/N]: y
[...]
Session sessionid successfully started, use 'renku session open --ssh sessionid' or 'ssh sessionid' to connect to it
Session <session-id> successfully started, use 'renku session open --ssh <session-id>' or 'ssh <session-id>' to
connect to it

This will create SSH keys for you and setup SSH configuration for connecting to the renku deployment.
You can then use the SSH connection name (``ssh renkulab.io-myproject-sessionid`` in the example)
Expand Down Expand Up @@ -277,13 +282,20 @@ def session_start_provider_options(*param_decls, **attrs):
@click.option("--image", type=click.STRING, metavar="<image_name>", help="Docker image to use for the session.")
@click.option("--cpu", type=click.FLOAT, metavar="<cpu quota>", help="CPUs quota for the session.")
@click.option("--disk", type=click.STRING, metavar="<disk size>", help="Amount of disk space required for the session.")
@click.option("--gpu", type=click.STRING, metavar="<GPU quota>", help="GPU quota for the session.")
@click.option(
"--gpu",
type=click.STRING,
metavar="<GPU quota>",
help="Number of GPU devices to add to the container ('all' to pass all GPUs).",
)
@click.option("--memory", type=click.STRING, metavar="<memory size>", help="Amount of memory required for the session.")
@session_start_provider_options()
def start(provider, config, image, cpu, disk, gpu, memory, **kwargs):
"""Start an interactive session."""
from renku.command.session import session_start_command

kwargs = {k: v for k, v in kwargs.items() if v is not None}

communicator = ClickCallback()
session_start_command().with_communicator(communicator).build().execute(
provider=provider,
Expand Down
3 changes: 2 additions & 1 deletion renku/ui/cli/utils/click.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def wrapper(f):
param_help = f"\b\n{param.help}\n " if j == 0 else param.help # NOTE: add newline after a group

args = (
[f"-{a}" if len(a) == 1 else f"--{a}" for a in param.flags if a] + [param.name]
[f"-{a}" if len(a) == 1 else f"--{a}" for a in param.flags if a] + [param.name.replace("-", "_")]
if param.flags
else [f"--{param.name}"]
)
Expand All @@ -141,6 +141,7 @@ def wrapper(f):
is_flag=param.is_flag,
default=param.default,
multiple=param.multiple,
metavar=param.metavar,
)(f)

name = f"{provider.name} configuration"
Expand Down
Loading