Skip to content

Commit

Permalink
Support job filtering by owner (#990)
Browse files Browse the repository at this point in the history
  • Loading branch information
Artem Yushkovskiy authored Aug 27, 2019
1 parent f6bca6d commit 1e36ca6
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.D/990.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support job filtering by owner: `neuro ps -o user-1 --owner=user-2`.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ neuro job ls [OPTIONS]
```bash

neuro ps -a
neuro ps -a --owner=user-1 --owner=user-2
neuro ps --name my-experiments-v1 -s failed -s succeeded
neuro ps --description=my favourite job
neuro ps -s failed -s succeeded -q
Expand All @@ -306,7 +307,8 @@ neuro ps -s failed -s succeeded -q

Name | Description|
|----|------------|
|_\-s, --status \[pending | running | succeeded | failed | all]_|Filter out job by status \(multiple option). Note: option `all` is deprecated, use `neuro ps -a` instead.|
|_\-s, --status \[pending | running | succeeded | failed | all]_|Filter out jobs by status \(multiple option). Note: option `all` is deprecated, use `neuro ps -a` instead.|
|_\-o, --owner TEXT_|Filter out jobs by owner \(multiple option).|
|_\-a, --all_|Show all jobs regardless the status \(equivalent to `\-s pending -s running -s succeeded -s failed`)|
|_\-n, --name NAME_|Filter out jobs by name|
|_\-d, --description DESCRIPTION_|Filter out jobs by description \(exact match)|
Expand Down Expand Up @@ -1386,6 +1388,7 @@ neuro ps [OPTIONS]
```bash

neuro ps -a
neuro ps -a --owner=user-1 --owner=user-2
neuro ps --name my-experiments-v1 -s failed -s succeeded
neuro ps --description=my favourite job
neuro ps -s failed -s succeeded -q
Expand All @@ -1396,7 +1399,8 @@ neuro ps -s failed -s succeeded -q

Name | Description|
|----|------------|
|_\-s, --status \[pending | running | succeeded | failed | all]_|Filter out job by status \(multiple option). Note: option `all` is deprecated, use `neuro ps -a` instead.|
|_\-s, --status \[pending | running | succeeded | failed | all]_|Filter out jobs by status \(multiple option). Note: option `all` is deprecated, use `neuro ps -a` instead.|
|_\-o, --owner TEXT_|Filter out jobs by owner \(multiple option).|
|_\-a, --all_|Show all jobs regardless the status \(equivalent to `\-s pending -s running -s succeeded -s failed`)|
|_\-n, --name NAME_|Filter out jobs by name|
|_\-d, --description DESCRIPTION_|Filter out jobs by description \(exact match)|
Expand Down
9 changes: 8 additions & 1 deletion neuromation/api/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ async def run(
return _job_description_from_api(res, parser)

async def list(
self, *, statuses: Optional[Set[JobStatus]] = None, name: Optional[str] = None
self,
*,
statuses: Optional[Set[JobStatus]] = None,
name: Optional[str] = None,
owners: Optional[Set[str]] = None,
) -> List[JobDescription]:
url = URL(f"jobs")
params: MultiDict[str] = MultiDict()
Expand All @@ -167,6 +171,9 @@ async def list(
params.add("status", status.value)
if name:
params.add("name", name)
if owners:
for owner in owners:
params.add("owner", owner)
parser = _ImageNameParser(
self._config.auth_token.username, self._config.cluster_config.registry_url
)
Expand Down
10 changes: 8 additions & 2 deletions neuromation/cli/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,13 @@ async def _print_logs(root: Root, job: str) -> None:
multiple=True,
type=click.Choice(["pending", "running", "succeeded", "failed", "all"]),
help=(
"Filter out job by status (multiple option)."
"Filter out jobs by status (multiple option)."
" Note: option `all` is deprecated, use `neuro ps -a` instead."
),
)
@click.option(
"-o", "--owner", multiple=True, help="Filter out jobs by owner (multiple option)."
)
@click.option(
"-a",
"--all",
Expand Down Expand Up @@ -441,6 +444,7 @@ async def ls(
status: Sequence[str],
all: bool,
name: str,
owner: Sequence[str],
description: str,
wide: bool,
) -> None:
Expand All @@ -450,13 +454,15 @@ async def ls(
Examples:
neuro ps -a
neuro ps -a --owner=user-1 --owner=user-2
neuro ps --name my-experiments-v1 -s failed -s succeeded
neuro ps --description="my favourite job"
neuro ps -s failed -s succeeded -q
"""

statuses = calc_statuses(status, all)
jobs = await root.client.jobs.list(statuses=statuses, name=name)
owners = set(owner)
jobs = await root.client.jobs.list(statuses=statuses, name=name, owners=owners)

# client-side filtering
if description:
Expand Down
56 changes: 54 additions & 2 deletions tests/api/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,11 @@ async def handler(request: web.Request) -> web.Response:


def create_job_response(
id: str, status: str, name: Optional[str] = None, image: str = "submit-image-name"
id: str,
status: str,
owner: str = "owner",
name: Optional[str] = None,
image: str = "submit-image-name",
) -> Dict[str, Any]:
result = {
"id": id,
Expand All @@ -1022,7 +1026,7 @@ def create_job_response(
},
},
"is_preemptible": True,
"owner": "owner",
"owner": owner,
}
if name:
result["name"] = name
Expand Down Expand Up @@ -1292,3 +1296,51 @@ async def handler(request: web.Request) -> web.Response:
)
job_descriptions = [_job_description_from_api(job, parser) for job in jobs]
assert ret == job_descriptions[:2]


async def test_list_filter_by_name_and_statuses_and_owners(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
name_1 = "job-name-1"
name_2 = "job-name-2"
owner_1 = "owner-1"
owner_2 = "owner-2"
jobs = [
create_job_response("job-id-1", "running", name=name_1, owner=owner_1),
create_job_response("job-id-2", "running", name=name_1, owner=owner_2),
create_job_response("job-id-3", "running", name=name_2, owner=owner_1),
create_job_response("job-id-4", "running", name=name_2, owner=owner_2),
create_job_response("job-id-5", "succeeded", name=name_1, owner=owner_1),
create_job_response("job-id-6", "succeeded", name=name_1, owner=owner_2),
create_job_response("job-id-7", "succeeded", name=name_2, owner=owner_1),
create_job_response("job-id-8", "succeeded", name=name_2, owner=owner_2),
]

async def handler(request: web.Request) -> web.Response:
statuses = request.query.getall("status")
name = request.query.get("name")
owners = request.query.getall("owner")
filtered_jobs = [
job
for job in jobs
if job["status"] in statuses
and job.get("name") == name
and job.get("owner") in owners
]
return web.json_response({"jobs": filtered_jobs})

app = web.Application()
app.router.add_get("/jobs", handler)
srv = await aiohttp_server(app)

statuses = {JobStatus.RUNNING}
name = name_1
owners = {owner_1, owner_2}
async with make_client(srv.make_url("/")) as client:
ret = await client.jobs.list(statuses=statuses, name=name, owners=owners)

parser = _ImageNameParser(
client._config.auth_token.username, client._config.cluster_config.registry_url
)
job_descriptions = [_job_description_from_api(job, parser) for job in jobs]
assert ret == job_descriptions[:2]

0 comments on commit 1e36ca6

Please sign in to comment.