diff --git a/CHANGELOG.D/928.feature b/CHANGELOG.D/928.feature new file mode 100644 index 000000000..ae22827c8 --- /dev/null +++ b/CHANGELOG.D/928.feature @@ -0,0 +1 @@ +The `-p/--non-preemptible` parameter for `neuro run` has been removed in favor in embedding it into presets coming from the server. diff --git a/README.md b/README.md index 1cc4d9bc6..9cc9584dd 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,6 @@ Name | Description| |_\-x, --extshm / -X, --no-extshm_|Request extended '/dev/shm' space \[default: True]| |_--http PORT_|Enable HTTP port forwarding to container \[default: 80]| |_\--http-auth / --no-http-auth_|Enable HTTP authentication for forwarded HTTP port \[default: True]| -|_\-p, --preemptible / -P, --non-preemptible_|Run job on a lower-cost preemptible instance \[default: False]| |_\-n, --name NAME_|Optional job name| |_\-d, --description DESC_|Optional job description in free format| |_\-q, --quiet_|Run command in quiet mode \(DEPRECATED)| @@ -1291,7 +1290,6 @@ Name | Description| |_\-x, --extshm / -X, --no-extshm_|Request extended '/dev/shm' space \[default: True]| |_--http PORT_|Enable HTTP port forwarding to container \[default: 80]| |_\--http-auth / --no-http-auth_|Enable HTTP authentication for forwarded HTTP port \[default: True]| -|_\-p, --preemptible / -P, --non-preemptible_|Run job on a lower-cost preemptible instance \[default: False]| |_\-n, --name NAME_|Optional job name| |_\-d, --description DESC_|Optional job description in free format| |_\-q, --quiet_|Run command in quiet mode \(DEPRECATED)| diff --git a/neuromation/api/login.py b/neuromation/api/login.py index 6bfe77e8b..e1798deb5 100644 --- a/neuromation/api/login.py +++ b/neuromation/api/login.py @@ -350,6 +350,7 @@ async def refresh(self, token: _AuthToken) -> _AuthToken: class RunPreset: cpu: float memory_mb: int + is_preemptible: bool = False gpu: Optional[int] = None gpu_model: Optional[str] = None @@ -593,6 +594,7 @@ async def get_server_config( memory_mb=data["memory_mb"], gpu=data.get("gpu"), gpu_model=data.get("gpu_model"), + is_preemptible=data.get("is_preemptible", False), ) for data in payload.get("resource_presets", ()) }, diff --git a/neuromation/cli/formatters/config.py b/neuromation/cli/formatters/config.py index 2c5ef5de2..5eaf247aa 100644 --- a/neuromation/cli/formatters/config.py +++ b/neuromation/cli/formatters/config.py @@ -1,3 +1,5 @@ +from sys import platform + from click import style from neuromation.cli.root import Root @@ -5,17 +7,22 @@ class ConfigFormatter: def __call__(self, root: Root) -> str: + if platform == "win32": + yes, no = "Yes", "No" + else: + yes, no = "✔︎", "✖︎" lines = [] lines.append(style("User Name", bold=True) + f": {root.username}") lines.append(style("API URL", bold=True) + f": {root.url}") lines.append(style("Docker Registry URL", bold=True) + f": {root.registry_url}") lines.append(style("Resource Presets", bold=True) + f":") indent = " " - lines.append(f"{indent}Name #CPU Memory #GPU GPU Model") + lines.append(f"{indent}Name #CPU Memory Preemptible #GPU GPU Model") for name, preset in root.resource_presets.items(): lines.append( ( - f"{indent}{name:12} {preset.cpu:>3} {preset.memory_mb:>7}" + f"{indent}{name:12} {preset.cpu:>3} {preset.memory_mb:>7} " + f"{yes if preset.is_preemptible else no:^11}" f" {preset.gpu or '':>3}" f" {preset.gpu_model or ''}" ).rstrip() diff --git a/neuromation/cli/job.py b/neuromation/cli/job.py index 7bab5fc3b..8730b1a78 100644 --- a/neuromation/cli/job.py +++ b/neuromation/cli/job.py @@ -596,9 +596,9 @@ def format_fail(job: str, reason: Exception) -> str: @click.option( "--preemptible/--non-preemptible", "-p/-P", - help="Run job on a lower-cost preemptible instance", - default=False, - show_default=True, + help="Run job on a lower-cost preemptible instance (DEPRECATED AND IGNORED)", + default=None, + hidden=True, ) @click.option( "-n", @@ -679,7 +679,7 @@ async def run( volume: Sequence[str], env: Sequence[str], env_file: Optional[str], - preemptible: bool, + preemptible: Optional[bool], name: Optional[str], description: Optional[str], wait_start: bool, @@ -709,6 +709,10 @@ async def run( if not preset: preset = next(iter(root.resource_presets.keys())) job_preset = root.resource_presets[preset] + if preemptible is not None: + click.echo( + "-p/-P option is deprecated and ignored. Use corresponding presets instead." + ) log.info(f"Using preset '{preset}': {job_preset}") @@ -727,7 +731,7 @@ async def run( volume=volume, env=env, env_file=env_file, - preemptible=preemptible, + preemptible=job_preset.is_preemptible, name=name, description=description, wait_start=wait_start, diff --git a/tests/cli/test_formatters.py b/tests/cli/test_formatters.py index 2f09234d7..717b364b3 100644 --- a/tests/cli/test_formatters.py +++ b/tests/cli/test_formatters.py @@ -2,6 +2,7 @@ import time from dataclasses import replace from datetime import datetime +from sys import platform from typing import Any, List, Optional import click @@ -1201,18 +1202,22 @@ def test_shm_container(self) -> None: class TestConfigFormatter: async def test_output(self, root: Root) -> None: out = ConfigFormatter()(root) + if platform == "win32": + no = "No" + else: + no = "✖︎" assert click.unstyle(out) == textwrap.dedent( - """\ + f"""\ User Configuration: User Name: user API URL: https://dev.neu.ro/api/v1 Docker Registry URL: https://registry-dev.neu.ro Resource Presets: - Name #CPU Memory #GPU GPU Model - gpu-small 7 30720 1 nvidia-tesla-k80 - gpu-large 7 61440 1 nvidia-tesla-v100 - cpu-small 7 2048 - cpu-large 7 14336""" + Name #CPU Memory Preemptible #GPU GPU Model + gpu-small 7 30720 {no} 1 nvidia-tesla-k80 + gpu-large 7 61440 {no} 1 nvidia-tesla-v100 + cpu-small 7 2048 {no} + cpu-large 7 14336 {no}""" ) diff --git a/tests/e2e/test_e2e_jobs.py b/tests/e2e/test_e2e_jobs.py index 6d7444ece..7e5daf249 100644 --- a/tests/e2e/test_e2e_jobs.py +++ b/tests/e2e/test_e2e_jobs.py @@ -831,7 +831,6 @@ def test_job_run(helper: Helper) -> None: "run", "-s", "cpu-small", - "--non-preemptible", "--no-wait-start", UBUNTU_IMAGE_NAME, command, @@ -908,7 +907,6 @@ def test_pass_config(image: str, helper: Helper) -> None: "-q", "-s", "cpu-small", - "--non-preemptible", "--no-wait-start", "--pass-config", image_full_str, @@ -943,7 +941,6 @@ def test_job_submit_bad_http_auth(helper: Helper, http_auth: str) -> None: "-g", "0", http_auth, - "--non-preemptible", "--no-wait-start", UBUNTU_IMAGE_NAME, "true", @@ -962,17 +959,7 @@ def fakebrowser(monkeypatch: Any) -> None: def test_job_browse(helper: Helper, fakebrowser: Any) -> None: # Run a new job captured = helper.run_cli( - [ - "-q", - "job", - "run", - "-s", - "cpu-small", - "--non-preemptible", - "--detach", - UBUNTU_IMAGE_NAME, - "true", - ] + ["-q", "job", "run", "-s", "cpu-small", "--detach", UBUNTU_IMAGE_NAME, "true"] ) job_id = captured.out @@ -993,7 +980,6 @@ def test_job_browse_named(helper: Helper, fakebrowser: Any) -> None: "run", "-s", "cpu-small", - "--non-preemptible", "--detach", "--name", job_name, @@ -1018,7 +1004,6 @@ def test_job_run_browse(helper: Helper, fakebrowser: Any) -> None: "run", "-s", "cpu-small", - "--non-preemptible", "--detach", "--browse", UBUNTU_IMAGE_NAME, @@ -1034,16 +1019,7 @@ def test_job_run_no_detach(helper: Helper) -> None: token = uuid4() # Run a new job captured = helper.run_cli( - [ - "-v", - "job", - "run", - "-s", - "cpu-small", - "--non-preemptible", - UBUNTU_IMAGE_NAME, - f"echo {token}", - ] + ["-v", "job", "run", "-s", "cpu-small", UBUNTU_IMAGE_NAME, f"echo {token}"] ) assert str(token) in captured.out @@ -1065,7 +1041,6 @@ def test_job_submit_no_detach_failure(helper: Helper) -> None: "0", "--http", "80", - "--non-preemptible", UBUNTU_IMAGE_NAME, f"exit 127", ] @@ -1089,7 +1064,6 @@ def test_job_submit_browse(helper: Helper, fakebrowser: Any) -> None: "0", "--http", "80", - "--non-preemptible", "--detach", "--browse", UBUNTU_IMAGE_NAME, diff --git a/tests/e2e/utils.py b/tests/e2e/utils.py index 5b620b136..18deb8c14 100644 --- a/tests/e2e/utils.py +++ b/tests/e2e/utils.py @@ -18,7 +18,7 @@ r"(?P.+)" ) -JOB_TINY_CONTAINER_PARAMS = ["-m", "20M", "-c", "0.1", "-g", "0", "--non-preemptible"] +JOB_TINY_CONTAINER_PARAMS = ["-m", "20M", "-c", "0.1", "-g", "0"] class JobWaitStateStopReached(AssertionError):