From 767bd60e3a10833a2679df6cfb57be872be3514a Mon Sep 17 00:00:00 2001 From: Artem Juszkowski Date: Tue, 26 Mar 2019 15:13:26 +0100 Subject: [PATCH 1/8] add full support for job names --- README.md | 24 ++--- neuromation/cli/formatters/jobs.py | 14 ++- neuromation/cli/job.py | 40 +++++--- neuromation/cli/utils.py | 23 ++++- python/CHANGELOG.D/648.feature | 1 + python/tests/cli/test_utils.py | 156 +++++++++++++++++++++++++++++ tests/cli/test_formatters.py | 16 +-- tests/client/test_jobs.py | 2 +- 8 files changed, 233 insertions(+), 43 deletions(-) create mode 100644 python/CHANGELOG.D/648.feature create mode 100644 python/tests/cli/test_utils.py diff --git a/README.md b/README.md index aa05a4035..a15cfe83d 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ Display status of a job. **Usage:** ```bash -neuro job status [OPTIONS] ID +neuro job status [OPTIONS] JOB ``` **Options:** @@ -269,7 +269,7 @@ Execute command in a running job. **Usage:** ```bash -neuro job exec [OPTIONS] ID CMD... +neuro job exec [OPTIONS] JOB CMD... ``` **Options:** @@ -290,7 +290,7 @@ Forward a port of a running job exposed with -ssh option to a local port. **Usage:** ```bash -neuro job port-forward [OPTIONS] ID LOCAL_PORT REMOTE_PORT +neuro job port-forward [OPTIONS] JOB LOCAL_PORT REMOTE_PORT ``` **Options:** @@ -310,7 +310,7 @@ Print the logs for a container. **Usage:** ```bash -neuro job logs [OPTIONS] ID +neuro job logs [OPTIONS] JOB ``` **Options:** @@ -329,7 +329,7 @@ Kill job\(s). **Usage:** ```bash -neuro job kill [OPTIONS] ID... +neuro job kill [OPTIONS] JOB_LIST... ``` **Options:** @@ -348,7 +348,7 @@ Display GPU/CPU/Memory usage. **Usage:** ```bash -neuro job top [OPTIONS] ID +neuro job top [OPTIONS] JOB ``` **Options:** @@ -955,7 +955,7 @@ Display status of a job. **Usage:** ```bash -neuro status [OPTIONS] ID +neuro status [OPTIONS] JOB ``` **Options:** @@ -974,7 +974,7 @@ Execute command in a running job. **Usage:** ```bash -neuro exec [OPTIONS] ID CMD... +neuro exec [OPTIONS] JOB CMD... ``` **Options:** @@ -995,7 +995,7 @@ Forward a port of a running job exposed with -ssh option to a local port. **Usage:** ```bash -neuro port-forward [OPTIONS] ID LOCAL_PORT REMOTE_PORT +neuro port-forward [OPTIONS] JOB LOCAL_PORT REMOTE_PORT ``` **Options:** @@ -1015,7 +1015,7 @@ Print the logs for a container. **Usage:** ```bash -neuro logs [OPTIONS] ID +neuro logs [OPTIONS] JOB ``` **Options:** @@ -1034,7 +1034,7 @@ Kill job\(s). **Usage:** ```bash -neuro kill [OPTIONS] ID... +neuro kill [OPTIONS] JOB_LIST... ``` **Options:** @@ -1053,7 +1053,7 @@ Display GPU/CPU/Memory usage. **Usage:** ```bash -neuro top [OPTIONS] ID +neuro top [OPTIONS] JOB ``` **Options:** diff --git a/neuromation/cli/formatters/jobs.py b/neuromation/cli/formatters/jobs.py index 6a1530c6c..ec8aaebed 100644 --- a/neuromation/cli/formatters/jobs.py +++ b/neuromation/cli/formatters/jobs.py @@ -49,19 +49,23 @@ def __call__(self, job: JobDescription) -> str: + f": {format_job_status(job.status)}" ) if job.name: - out.append(style("Name", bold=True) + f": {job.name}") + out.append(style("Job name", bold=True) + f": {job.name}") + job_alias = job.name + else: + job_alias = job.id if job.http_url: out.append(style("Http URL", bold=True) + f": {job.http_url}") out.append(style("Shortcuts", bold=True) + ":") - out.append(f" neuro status {job.id} " + style("# check job status", dim=True)) + + out.append(f" neuro status {job_alias} " + style("# check job status", dim=True)) out.append( - f" neuro logs {job.id} " + style("# monitor job stdout", dim=True) + f" neuro logs {job_alias} " + style("# monitor job stdout", dim=True) ) out.append( - f" neuro top {job.id} " + f" neuro top {job_alias} " + style("# display real-time job telemetry", dim=True) ) - out.append(f" neuro kill {job.id} " + style("# kill job", dim=True)) + out.append(f" neuro kill {job_alias} " + style("# kill job", dim=True)) return "\n".join(out) diff --git a/neuromation/cli/job.py b/neuromation/cli/job.py index 578d7df67..e3940f338 100644 --- a/neuromation/cli/job.py +++ b/neuromation/cli/job.py @@ -37,7 +37,7 @@ ) from .rc import Config from .ssh_utils import connect_ssh -from .utils import alias, async_cmd, command, group, volume_to_verbose_str +from .utils import alias, async_cmd, command, group, resolve_job, volume_to_verbose_str log = logging.getLogger(__name__) @@ -266,7 +266,7 @@ async def submit( @command(context_settings=dict(ignore_unknown_options=True)) -@click.argument("id") +@click.argument("job") @click.argument("cmd", nargs=-1, type=click.UNPROCESSED, required=True) @click.option( "-t", @@ -281,19 +281,20 @@ async def submit( ) @async_cmd async def exec( - cfg: Config, id: str, tty: bool, no_key_check: bool, cmd: Sequence[str] + cfg: Config, job: str, tty: bool, no_key_check: bool, cmd: Sequence[str] ) -> None: """ Execute command in a running job. """ cmd = shlex.split(" ".join(cmd)) async with cfg.make_client() as client: + id = await resolve_job(client, job) retcode = await client.jobs.exec(id, tty, no_key_check, cmd) sys.exit(retcode) @command(context_settings=dict(ignore_unknown_options=True)) -@click.argument("id") +@click.argument("job") @click.argument("local_port", type=int) @click.argument("remote_port", type=int) @click.option( @@ -303,13 +304,14 @@ async def exec( ) @async_cmd async def port_forward( - cfg: Config, id: str, no_key_check: bool, local_port: int, remote_port: int + cfg: Config, job: str, no_key_check: bool, local_port: int, remote_port: int ) -> None: """ Forward a port of a running job exposed with -ssh option to a local port. """ async with cfg.make_client() as client: + id = await resolve_job(client, job) retcode = await client.jobs.port_forward( id, no_key_check, local_port, remote_port ) @@ -317,13 +319,13 @@ async def port_forward( @command(deprecated=True, hidden=True) -@click.argument("id") +@click.argument("job") @click.option( "--user", help="Container user name", default=JOB_SSH_USER, show_default=True ) @click.option("--key", help="Path to container private key.") @async_cmd -async def ssh(cfg: Config, id: str, user: str, key: str) -> None: +async def ssh(cfg: Config, job: str, user: str, key: str) -> None: """ Starts ssh terminal connected to running job. @@ -336,13 +338,14 @@ async def ssh(cfg: Config, id: str, user: str, key: str) -> None: git_key = cfg.github_rsa_path async with cfg.make_client() as client: + id = await resolve_job(client, job) await connect_ssh(client, id, git_key, user, key) @command() -@click.argument("id") +@click.argument("job") @async_cmd -async def logs(cfg: Config, id: str) -> None: +async def logs(cfg: Config, job: str) -> None: """ Print the logs for a container. """ @@ -351,6 +354,7 @@ async def logs(cfg: Config, id: str) -> None: ) async with cfg.make_client(timeout=timeout) as client: + id = await resolve_job(client, job) async for chunk in client.jobs.monitor(id): if not chunk: break @@ -426,26 +430,28 @@ async def ls( @command() -@click.argument("id") +@click.argument("job") @async_cmd -async def status(cfg: Config, id: str) -> None: +async def status(cfg: Config, job: str) -> None: """ Display status of a job. """ async with cfg.make_client() as client: + id = await resolve_job(client, job) res = await client.jobs.status(id) click.echo(JobStatusFormatter()(res)) @command() -@click.argument("id") +@click.argument("job") @async_cmd -async def top(cfg: Config, id: str) -> None: +async def top(cfg: Config, job: str) -> None: """ Display GPU/CPU/Memory usage. """ formatter = JobTelemetryFormatter() async with cfg.make_client() as client: + id = await resolve_job(client, job) print_header = True async for res in client.jobs.top(id): if print_header: @@ -456,17 +462,19 @@ async def top(cfg: Config, id: str) -> None: @command() -@click.argument("id", nargs=-1, required=True) +@click.argument("job_list", nargs=-1, required=True) @async_cmd -async def kill(cfg: Config, id: Sequence[str]) -> None: +async def kill(cfg: Config, job_list: Sequence[str]) -> None: """ Kill job(s). """ errors = [] async with cfg.make_client() as client: - for job in id: + for job in job_list: + job = await resolve_job(client, job) try: await client.jobs.kill(job) + # TODO (ajuszkowski) printing should be on the cli level print(job) except ValueError as e: errors.append((job, e)) diff --git a/neuromation/cli/utils.py b/neuromation/cli/utils.py index 170c3d94b..b408629d2 100644 --- a/neuromation/cli/utils.py +++ b/neuromation/cli/utils.py @@ -1,4 +1,5 @@ import asyncio +import logging import re import shlex import sys @@ -19,13 +20,15 @@ import click -from neuromation.client import Volume +from neuromation.client import Client, JobDescription, Volume from neuromation.utils import run from .rc import Config, ConfigFactory, save from .version_utils import AbstractVersionChecker, DummyVersionChecker, VersionChecker +log = logging.getLogger(__name__) + _T = TypeVar("_T") DEPRECATED_HELP_NOTICE = " " + click.style("(DEPRECATED)", fg="red") @@ -322,3 +325,21 @@ def volume_to_verbose_str(volume: Volume) -> str: f"'{volume.storage_path}' mounted to '{volume.container_path}' " f"in {('ro' if volume.read_only else 'rw')} mode" ) + + +async def resolve_job(client: Client, id_or_name: str) -> str: + jobs: List[JobDescription] = [] + try: + jobs = await client.jobs.list(name=id_or_name) + except Exception as e: + log.error( + f"Failed to resolve job-name '{id_or_name} to job-ID': {e}. " + f"Using '{id_or_name}' as job-ID" + ) + if jobs: + job_id = jobs[-1].id + log.debug(f"Job name '{id_or_name}' resolved to job ID '{job_id}'") + else: + job_id = id_or_name + + return job_id diff --git a/python/CHANGELOG.D/648.feature b/python/CHANGELOG.D/648.feature new file mode 100644 index 000000000..626619cb5 --- /dev/null +++ b/python/CHANGELOG.D/648.feature @@ -0,0 +1 @@ +Support job names. diff --git a/python/tests/cli/test_utils.py b/python/tests/cli/test_utils.py new file mode 100644 index 000000000..7dba66425 --- /dev/null +++ b/python/tests/cli/test_utils.py @@ -0,0 +1,156 @@ +from aiohttp import web + +from neuromation.cli.utils import resolve_job +from neuromation.client import Client + + +async def test_resolve_job_id__no_jobs_found(aiohttp_server, token): + JSON = {"jobs": []} + job_id = "job-81839be3-3ecf-4ec5-80d9-19b1588869db" + job_name_to_resolve = job_id + + async def handler(request): + assert request.query.get("name") == job_name_to_resolve + return web.json_response(JSON) + + app = web.Application() + app.router.add_get("/jobs", handler) + + srv = await aiohttp_server(app) + + async with Client(srv.make_url("/"), token) as client: + resolved = await resolve_job(client, job_name_to_resolve) + assert resolved == job_id + + +async def test_resolve_job_id__single_job_found(aiohttp_server, token): + job_name_to_resolve = "test-job-name-555" + JSON = { + "jobs": [ + { + "id": "job-efb7d723-722c-4d5c-a5db-de258db4b09e", + "owner": "test1", + "status": "running", + "history": { + "status": "running", + "reason": None, + "description": None, + "created_at": "2019-03-18T12:41:10.573468+00:00", + "started_at": "2019-03-18T12:41:16.804040+00:00", + }, + "container": { + "image": "ubuntu:latest", + "env": {}, + "volumes": [], + "command": "sleep 1h", + "resources": {"cpu": 0.1, "memory_mb": 1024, "shm": True}, + }, + "ssh_auth_server": "ssh://nobody@ssh-auth-dev.neu.ro:22", + "is_preemptible": True, + "name": job_name_to_resolve, + "internal_hostname": "job-efb7d723-722c-4d5c-a5db-de258db4b09e.default", + } + ] + } + job_id = JSON["jobs"][0]["id"] + + async def handler(request): + assert request.query.get("name") == job_name_to_resolve + return web.json_response(JSON) + + app = web.Application() + app.router.add_get("/jobs", handler) + + srv = await aiohttp_server(app) + + async with Client(srv.make_url("/"), token) as client: + resolved = await resolve_job(client, job_name_to_resolve) + assert resolved == job_id + + +async def test_resolve_job_id__multiple_jobs_found(aiohttp_server, token): + job_name_to_resolve = "job-name-123-000" + JSON = { + "jobs": [ + { + "id": "job-d912aa8c-d01b-44bd-b77c-5a19fc151f89", + "owner": "test1", + "status": "succeeded", + "history": { + "status": "succeeded", + "reason": None, + "description": None, + "created_at": "2019-03-17T16:24:54.746175+00:00", + "started_at": "2019-03-17T16:25:00.868880+00:00", + "finished_at": "2019-03-17T16:28:01.298487+00:00", + }, + "container": { + "image": "ubuntu:latest", + "env": {}, + "volumes": [], + "command": "sleep 3m", + "resources": {"cpu": 0.1, "memory_mb": 1024, "shm": True}, + }, + "ssh_auth_server": "ssh://nobody@ssh-auth-dev.neu.ro:22", + "is_preemptible": True, + "name": job_name_to_resolve, + "internal_hostname": "job-d912aa8c-d01b-44bd-b77c-5a19fc151f89.default", + }, + { + "id": "job-e5071b6b-2e97-4cce-b12d-86e31751dc8a", + "owner": "test1", + "status": "succeeded", + "history": { + "status": "succeeded", + "reason": None, + "description": None, + "created_at": "2019-03-18T11:31:03.669549+00:00", + "started_at": "2019-03-18T11:31:10.428975+00:00", + "finished_at": "2019-03-18T11:31:54.896666+00:00", + }, + "container": { + "image": "ubuntu:latest", + "env": {}, + "volumes": [], + "command": "sleep 5m", + "resources": {"cpu": 0.1, "memory_mb": 1024, "shm": True}, + }, + "ssh_auth_server": "ssh://nobody@ssh-auth-dev.neu.ro:22", + "is_preemptible": True, + "name": job_name_to_resolve, + "internal_hostname": "job-e5071b6b-2e97-4cce-b12d-86e31751dc8a.default", + }, + ] + } + job_id = JSON["jobs"][-1]["id"] + + async def handler(request): + assert request.query.get("name") == job_name_to_resolve + return web.json_response(JSON) + + app = web.Application() + app.router.add_get("/jobs", handler) + + srv = await aiohttp_server(app) + + async with Client(srv.make_url("/"), token) as client: + resolved = await resolve_job(client, job_name_to_resolve) + assert resolved == job_id + + +async def test_resolve_job_id__server_error(aiohttp_server, token): + job_id = "job-81839be3-3ecf-4ec5-80d9-19b1588869db" + job_name_to_resolve = job_id + + async def handler(request): + assert request.query.get("name") == job_name_to_resolve + raise web.HTTPError() + + app = web.Application() + app.router.add_get("/jobs", handler) + + srv = await aiohttp_server(app) + + async with Client(srv.make_url("/"), token) as client: + resolved = await resolve_job(client, job_name_to_resolve) + assert resolved == job_id diff --git a/tests/cli/test_formatters.py b/tests/cli/test_formatters.py index f4e14638b..1310231eb 100644 --- a/tests/cli/test_formatters.py +++ b/tests/cli/test_formatters.py @@ -118,10 +118,10 @@ def test_non_quiet(self, job_descr) -> None: f"Job ID: {TEST_JOB_ID} Status: {JobStatus.PENDING}\n" + f"Name: {TEST_JOB_NAME}\n" + f"Shortcuts:\n" - + f" neuro status {TEST_JOB_ID} # check job status\n" - + f" neuro logs {TEST_JOB_ID} # monitor job stdout\n" - + f" neuro top {TEST_JOB_ID} # display real-time job telemetry\n" - + f" neuro kill {TEST_JOB_ID} # kill job" + + f" neuro status {TEST_JOB_NAME} # check job status\n" + + f" neuro logs {TEST_JOB_NAME} # monitor job stdout\n" + + f" neuro top {TEST_JOB_NAME} # display real-time job telemetry\n" + + f" neuro kill {TEST_JOB_NAME} # kill job" ) assert click.unstyle(JobFormatter(quiet=False)(job_descr)) == expected @@ -145,10 +145,10 @@ def test_non_quiet_http_url(self, job_descr) -> None: + f"Name: {TEST_JOB_NAME}\n" + f"Http URL: https://job.dev\n" + f"Shortcuts:\n" - + f" neuro status {TEST_JOB_ID} # check job status\n" - + f" neuro logs {TEST_JOB_ID} # monitor job stdout\n" - + f" neuro top {TEST_JOB_ID} # display real-time job telemetry\n" - + f" neuro kill {TEST_JOB_ID} # kill job" + + f" neuro status {TEST_JOB_NAME} # check job status\n" + + f" neuro logs {TEST_JOB_NAME} # monitor job stdout\n" + + f" neuro top {TEST_JOB_NAME} # display real-time job telemetry\n" + + f" neuro kill {TEST_JOB_NAME} # kill job" ) assert click.unstyle(JobFormatter(quiet=False)(job_descr)) == expected diff --git a/tests/client/test_jobs.py b/tests/client/test_jobs.py index f899bada9..f34fa17ea 100644 --- a/tests/client/test_jobs.py +++ b/tests/client/test_jobs.py @@ -3,8 +3,8 @@ import pytest from aiohttp import web +from neuromation.cli.rc import Client from neuromation.client import ( - Client, Image, JobDescription, NetworkPortForwarding, From 7753914dea078207236334b158693b3cc356426a Mon Sep 17 00:00:00 2001 From: Artem Juszkowski Date: Tue, 26 Mar 2019 19:13:57 +0100 Subject: [PATCH 2/8] add some e2e tests + make lint + fix error msg --- neuromation/cli/formatters/jobs.py | 4 +++- neuromation/cli/utils.py | 5 +---- tests/e2e/test_e2e.py | 11 ++++++---- tests/e2e/test_e2e_jobs.py | 32 +++++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/neuromation/cli/formatters/jobs.py b/neuromation/cli/formatters/jobs.py index ec8aaebed..691ec9de9 100644 --- a/neuromation/cli/formatters/jobs.py +++ b/neuromation/cli/formatters/jobs.py @@ -57,7 +57,9 @@ def __call__(self, job: JobDescription) -> str: out.append(style("Http URL", bold=True) + f": {job.http_url}") out.append(style("Shortcuts", bold=True) + ":") - out.append(f" neuro status {job_alias} " + style("# check job status", dim=True)) + out.append( + f" neuro status {job_alias} " + style("# check job status", dim=True) + ) out.append( f" neuro logs {job_alias} " + style("# monitor job stdout", dim=True) ) diff --git a/neuromation/cli/utils.py b/neuromation/cli/utils.py index b408629d2..69b66ecc5 100644 --- a/neuromation/cli/utils.py +++ b/neuromation/cli/utils.py @@ -332,10 +332,7 @@ async def resolve_job(client: Client, id_or_name: str) -> str: try: jobs = await client.jobs.list(name=id_or_name) except Exception as e: - log.error( - f"Failed to resolve job-name '{id_or_name} to job-ID': {e}. " - f"Using '{id_or_name}' as job-ID" - ) + log.error(f"Failed to resolve job-name '{id_or_name}' to a job-ID: {e}") if jobs: job_id = jobs[-1].id log.debug(f"Job name '{id_or_name}' resolved to job ID '{job_id}'") diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index e77356ca1..c31866054 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -1,4 +1,5 @@ import re +from uuid import uuid4 import pytest @@ -54,18 +55,20 @@ def split_non_empty_parts(line, separator=None): "do sleep 1; let COUNTER+=1; done; sleep 30" ) command = f"bash -c '{bash_script}'" + job_name = f"test-job-{uuid4()}" + aux_params = ["--volume", f"{helper.tmpstorage}:/data:ro", "--name", job_name] job_id = helper.run_job_and_wait_state( - UBUNTU_IMAGE_NAME, - command, - JOB_TINY_CONTAINER_PARAMS + ["--volume", f"{helper.tmpstorage}:/data:ro"], + image=UBUNTU_IMAGE_NAME, + command=command, + params=JOB_TINY_CONTAINER_PARAMS + aux_params, ) # the job is running # upload a file and unblock the job helper.check_upload_file_to_storage("dummy", "", __file__) - captured = helper.run_cli(["job", "top", job_id]) + captured = helper.run_cli(["job", "top", job_name]) header_line, top_line = split_non_empty_parts(captured.out, separator="\n") header_parts = split_non_empty_parts(header_line, separator="\t") diff --git a/tests/e2e/test_e2e_jobs.py b/tests/e2e/test_e2e_jobs.py index ee5fd9c1d..7b59c46d8 100644 --- a/tests/e2e/test_e2e_jobs.py +++ b/tests/e2e/test_e2e_jobs.py @@ -80,8 +80,8 @@ def test_job_lifecycle(helper): assert job_id in store_out assert command not in store_out - # Kill the job - captured = helper.run_cli(["job", "kill", job_id]) + # Kill the job by name + captured = helper.run_cli(["job", "kill", job_name]) # Currently we check that the job is not running anymore # TODO(adavydow): replace to succeeded check when racecon in @@ -100,6 +100,17 @@ def test_job_lifecycle(helper): assert job_name in store_out + # Check job status by id + captured = helper.run_cli(["job", "status", job_id]) + store_out = captured.out + assert store_out.startswith(f"Job: {job_id}\nName: {job_name}") + + # Check job status by name + captured = helper.run_cli(["job", "status", job_name]) + store_out = captured.out + assert store_out.startswith(f"Job: {job_id}\nName: {job_name}") + + @pytest.mark.e2e def test_job_description(helper): # Remember original running jobs @@ -634,6 +645,7 @@ def test_e2e_multiple_env_from_file(helper, tmp_path): @pytest.mark.e2e def test_e2e_ssh_exec_true(helper): + job_name = f"test-job-{uuid4()}" command = 'bash -c "sleep 15m; false"' captured = helper.run_cli( [ @@ -645,6 +657,8 @@ def test_e2e_ssh_exec_true(helper): "0.1", "--non-preemptible", "--no-wait-start", + "-n", + job_name, UBUNTU_IMAGE_NAME, command, ] @@ -657,6 +671,9 @@ def test_e2e_ssh_exec_true(helper): captured = helper.run_cli(["job", "exec", "--no-key-check", job_id, "true"]) assert captured.out == "" + captured = helper.run_cli(["job", "exec", "--no-key-check", job_name, "true"]) + assert captured.out == "" + @pytest.mark.e2e def test_e2e_ssh_exec_false(helper): @@ -1017,8 +1034,17 @@ async def get_(url): @pytest.mark.e2e def test_port_forward_no_job(helper, nginx_job): + job_name = f"non-existing-job-{uuid4()}" + with pytest.raises(SystemExit) as cm: + helper.run_cli(["port-forward", "--no-key-check", job_name, "0", "0"]) + assert cm.value.code == 127 + + +@pytest.mark.e2e +def test_exec_no_job(helper, nginx_job): + job_name = f"non-existing-job-{uuid4()}" with pytest.raises(SystemExit) as cm: - helper.run_cli(["port-forward", "--no-key-check", "nojob", "0", "0"]) + helper.run_cli(["exec", "--no-key-check", job_name, "true"]) assert cm.value.code == 127 From 0de3007da95467487c0a12e01cd17bf517f1f362 Mon Sep 17 00:00:00 2001 From: Artem Juszkowski Date: Tue, 26 Mar 2019 19:16:07 +0100 Subject: [PATCH 3/8] make lint --- tests/e2e/test_e2e.py | 2 +- tests/e2e/test_e2e_jobs.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index c31866054..78d691ed2 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -58,7 +58,7 @@ def split_non_empty_parts(line, separator=None): job_name = f"test-job-{uuid4()}" aux_params = ["--volume", f"{helper.tmpstorage}:/data:ro", "--name", job_name] - job_id = helper.run_job_and_wait_state( + helper.run_job_and_wait_state( image=UBUNTU_IMAGE_NAME, command=command, params=JOB_TINY_CONTAINER_PARAMS + aux_params, diff --git a/tests/e2e/test_e2e_jobs.py b/tests/e2e/test_e2e_jobs.py index 7b59c46d8..178a0f0d2 100644 --- a/tests/e2e/test_e2e_jobs.py +++ b/tests/e2e/test_e2e_jobs.py @@ -99,7 +99,6 @@ def test_job_lifecycle(helper): assert job_id in store_out assert job_name in store_out - # Check job status by id captured = helper.run_cli(["job", "status", job_id]) store_out = captured.out From 130f5359e1eb2fbfbdc30ed2a9dd19a02ed79c9e Mon Sep 17 00:00:00 2001 From: Artem Juszkowski Date: Thu, 28 Mar 2019 06:34:56 +0100 Subject: [PATCH 4/8] rebase conflict --- neuromation/cli/formatters/jobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neuromation/cli/formatters/jobs.py b/neuromation/cli/formatters/jobs.py index 691ec9de9..8657b8810 100644 --- a/neuromation/cli/formatters/jobs.py +++ b/neuromation/cli/formatters/jobs.py @@ -49,7 +49,7 @@ def __call__(self, job: JobDescription) -> str: + f": {format_job_status(job.status)}" ) if job.name: - out.append(style("Job name", bold=True) + f": {job.name}") + out.append(style("Name", bold=True) + f": {job.name}") job_alias = job.name else: job_alias = job.id From f1056c79f5fcb89780bac3e74c7277c156d21029 Mon Sep 17 00:00:00 2001 From: Artem Yushkovskiy Date: Wed, 3 Apr 2019 16:45:09 +0300 Subject: [PATCH 5/8] review: rename job_list -> jobs --- neuromation/cli/job.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/neuromation/cli/job.py b/neuromation/cli/job.py index e3940f338..2d3e2a808 100644 --- a/neuromation/cli/job.py +++ b/neuromation/cli/job.py @@ -462,20 +462,20 @@ async def top(cfg: Config, job: str) -> None: @command() -@click.argument("job_list", nargs=-1, required=True) +@click.argument("jobs", nargs=-1, required=True) @async_cmd -async def kill(cfg: Config, job_list: Sequence[str]) -> None: +async def kill(cfg: Config, jobs: Sequence[str]) -> None: """ Kill job(s). """ errors = [] async with cfg.make_client() as client: - for job in job_list: - job = await resolve_job(client, job) + for job in jobs: + job_resolved = await resolve_job(client, job) try: - await client.jobs.kill(job) + await client.jobs.kill(job_resolved) # TODO (ajuszkowski) printing should be on the cli level - print(job) + print(job_resolved) except ValueError as e: errors.append((job, e)) From dc6ccc8435de8dfcb82331770a6755d8d64218f5 Mon Sep 17 00:00:00 2001 From: Artem Yushkovskiy Date: Wed, 3 Apr 2019 16:48:29 +0300 Subject: [PATCH 6/8] review: move changelog file --- {python/CHANGELOG.D => CHANGELOG.D}/648.feature | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {python/CHANGELOG.D => CHANGELOG.D}/648.feature (100%) diff --git a/python/CHANGELOG.D/648.feature b/CHANGELOG.D/648.feature similarity index 100% rename from python/CHANGELOG.D/648.feature rename to CHANGELOG.D/648.feature From abba5ff7a29972a0c4f73cdd8d2d63bde2086ec0 Mon Sep 17 00:00:00 2001 From: Artem Yushkovskiy Date: Wed, 3 Apr 2019 16:49:26 +0300 Subject: [PATCH 7/8] review: move test file out from python directory --- {python/tests => tests}/cli/test_utils.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {python/tests => tests}/cli/test_utils.py (100%) diff --git a/python/tests/cli/test_utils.py b/tests/cli/test_utils.py similarity index 100% rename from python/tests/cli/test_utils.py rename to tests/cli/test_utils.py From c09bafcea2efe97008f8cb00c05e9f14fa6d361b Mon Sep 17 00:00:00 2001 From: Artem Yushkovskiy Date: Wed, 3 Apr 2019 16:49:40 +0300 Subject: [PATCH 8/8] make format: readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a15cfe83d..261ff2b58 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ Kill job\(s). **Usage:** ```bash -neuro job kill [OPTIONS] JOB_LIST... +neuro job kill [OPTIONS] JOBS... ``` **Options:** @@ -1034,7 +1034,7 @@ Kill job\(s). **Usage:** ```bash -neuro kill [OPTIONS] JOB_LIST... +neuro kill [OPTIONS] JOBS... ``` **Options:**