Skip to content

Commit 46340ca

Browse files
Merge pull request #4609 from bruntib/async_store_2
feat(cmd): Implemented a CLI for task management
2 parents 8e32dc3 + c1b573f commit 46340ca

File tree

9 files changed

+1283
-14
lines changed

9 files changed

+1283
-14
lines changed

codechecker_common/typehints.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# -------------------------------------------------------------------------
2+
#
3+
# Part of the CodeChecker project, under the Apache License v2.0 with
4+
# LLVM Exceptions. See LICENSE for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
#
7+
# -------------------------------------------------------------------------
8+
"""
9+
Type hint (`typing`) extensions.
10+
"""
11+
from typing import Any, Protocol, TypeVar
12+
13+
14+
_T_contra = TypeVar("_T_contra", contravariant=True)
15+
16+
17+
class LTComparable(Protocol[_T_contra]):
18+
def __lt__(self, other: _T_contra, /) -> bool: ...
19+
20+
21+
class LEComparable(Protocol[_T_contra]):
22+
def __le__(self, other: _T_contra, /) -> bool: ...
23+
24+
25+
class GTComparable(Protocol[_T_contra]):
26+
def __gt__(self, other: _T_contra, /) -> bool: ...
27+
28+
29+
class GEComparable(Protocol[_T_contra]):
30+
def __ge__(self, other: _T_contra, /) -> bool: ...
31+
32+
33+
# pylint: disable=too-many-ancestors
34+
class Orderable(LTComparable[Any], LEComparable[Any],
35+
GTComparable[Any], GEComparable[Any], Protocol):
36+
"""Type hint for something that supports rich comparison operators."""

codechecker_common/util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
from codechecker_common.logger import get_logger
2626

27+
from .typehints import Orderable
28+
2729
LOG = get_logger('system')
2830

2931

@@ -39,7 +41,7 @@ def arg_match(options, args):
3941
return matched_args
4042

4143

42-
def clamp(min_: int, value: int, max_: int) -> int:
44+
def clamp(min_: Orderable, value: Orderable, max_: Orderable) -> Orderable:
4345
"""Clamps ``value`` such that ``min_ <= value <= max_``."""
4446
if min_ > max_:
4547
raise ValueError("min <= max required")

docs/web/user_guide.md

Lines changed: 290 additions & 0 deletions
Large diffs are not rendered by default.

web/client/codechecker_client/cli/cmd.py

Lines changed: 268 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
import argparse
1515
import getpass
1616
import datetime
17+
import os
1718
import sys
1819

1920
from codechecker_api.codeCheckerDBAccess_v6 import ttypes
2021

21-
from codechecker_client import cmd_line_client
22-
from codechecker_client import product_client
23-
from codechecker_client import permission_client, source_component_client, \
22+
from codechecker_client import \
23+
cmd_line_client, \
24+
permission_client, \
25+
product_client, \
26+
source_component_client, \
27+
task_client, \
2428
token_client
2529

2630
from codechecker_common import arg, logger, util
@@ -1229,6 +1233,231 @@ def __register_permissions(parser):
12291233
help="The output format to use in showing the data.")
12301234

12311235

1236+
def __register_tasks(parser):
1237+
"""
1238+
Add `argparse` subcommand `parser` options for the "handle server-side
1239+
tasks" action.
1240+
"""
1241+
if "TEST_WORKSPACE" in os.environ:
1242+
testing_args = parser.add_argument_group("testing arguments")
1243+
testing_args.add_argument("--create-dummy-task",
1244+
dest="dummy_task_args",
1245+
metavar="ARG",
1246+
default=argparse.SUPPRESS,
1247+
type=str,
1248+
nargs=2,
1249+
help="""
1250+
Exercises the 'createDummyTask(int timeout, bool shouldFail)' API endpoint.
1251+
Used for testing purposes.
1252+
Note, that the server **MUST** be started in a testing environment as well,
1253+
otherwise, the request will be rejected by the server!
1254+
""")
1255+
1256+
parser.add_argument("-t", "--token",
1257+
dest="token",
1258+
metavar="TOKEN",
1259+
type=str,
1260+
nargs='*',
1261+
help="The identifying token(s) of the task(s) to "
1262+
"query. Each task is associated with a unique "
1263+
"token.")
1264+
1265+
parser.add_argument("--await",
1266+
dest="wait_and_block",
1267+
action="store_true",
1268+
help="""
1269+
Instead of querying the status and reporting that, followed by an exit, block
1270+
execution of the 'CodeChecker cmd serverside-tasks' program until the queried
1271+
task(s) terminate(s).
1272+
Makes the CLI's return code '0' if the task(s) completed successfully, and
1273+
non-zero otherwise.
1274+
If '--kill' is also specified, the CLI will await the shutdown of the task(s),
1275+
but will return '0' if the task(s) were successfully killed as well.
1276+
""")
1277+
1278+
parser.add_argument("--kill",
1279+
dest="cancel_task",
1280+
action="store_true",
1281+
help="""
1282+
Request the co-operative and graceful termination of the tasks matching the
1283+
filter(s) specified.
1284+
'--kill' is only available to SUPERUSERs!
1285+
Note, that this action only submits a *REQUEST* of termination to the server,
1286+
and tasks are free to not support in-progress kills.
1287+
Even for tasks that support getting killed, due to its graceful nature, it
1288+
might take a considerable time for the killing to conclude.
1289+
Killing a task that has not started RUNNING yet results in it automatically
1290+
terminating before it would start.
1291+
""")
1292+
1293+
output = parser.add_argument_group("output arguments")
1294+
output.add_argument("--output",
1295+
dest="output_format",
1296+
required=False,
1297+
default="plaintext",
1298+
choices=["plaintext", "table", "json"],
1299+
help="The format of the output to use when showing "
1300+
"the result of the request.")
1301+
1302+
task_list = parser.add_argument_group(
1303+
"task list filter arguments",
1304+
"""These options can be used to obtain and filter the list of tasks
1305+
associated with the 'CodeChecker server' specified by '--url', based on the
1306+
various information columns stored for tasks.
1307+
1308+
'--token' is usable with the following filters as well.
1309+
1310+
Filters with a variable number of options (e.g., '--machine-id A B') will be
1311+
in a Boolean OR relation with each other (meaning: machine ID is either "A"
1312+
or "B").
1313+
Specifying multiple filters (e.g., '--machine-id A B --username John') will
1314+
be considered in a Boolean AND relation (meaning: [machine ID is either "A" or
1315+
"B"] and [the task was created by "John"]).
1316+
1317+
Listing is only available for the following, privileged users:
1318+
- For tasks that are associated with a specific product, the PRODUCT_ADMINs
1319+
of that product.
1320+
- Server administrators (SUPERUSERs).
1321+
1322+
Unprivileged users MUST use only the task's token to query information about
1323+
the task.
1324+
""")
1325+
1326+
task_list.add_argument("--machine-id",
1327+
type=str,
1328+
nargs='*',
1329+
help="The IDs of the server instance executing "
1330+
"the tasks. This is an internal identifier "
1331+
"set by server administrators via the "
1332+
"'CodeChecker server' command.")
1333+
1334+
task_list.add_argument("--type",
1335+
type=str,
1336+
nargs='*',
1337+
help="The descriptive, but still "
1338+
"machine-readable \"type\" of the tasks to "
1339+
"filter for.")
1340+
1341+
task_list.add_argument("--status",
1342+
type=str,
1343+
nargs='*',
1344+
choices=["allocated", "enqueued", "running",
1345+
"completed", "failed", "cancelled",
1346+
"dropped"],
1347+
help="The task's execution status(es) in the "
1348+
"pipeline.")
1349+
1350+
username = task_list.add_mutually_exclusive_group(required=False)
1351+
username.add_argument("--username",
1352+
type=str,
1353+
nargs='*',
1354+
help="The user(s) who executed the action that "
1355+
"caused the tasks' creation.")
1356+
username.add_argument("--no-username",
1357+
action="store_true",
1358+
help="Filter for tasks without a responsible user "
1359+
"that created them.")
1360+
1361+
product = task_list.add_mutually_exclusive_group(required=False)
1362+
product.add_argument("--product",
1363+
type=str,
1364+
nargs='*',
1365+
help="Filter for tasks that execute in the context "
1366+
"of products specified by the given ENDPOINTs. "
1367+
"This query is only available if you are a "
1368+
"PRODUCT_ADMIN of the specified product(s).")
1369+
product.add_argument("--no-product",
1370+
action="store_true",
1371+
help="Filter for server-wide tasks (not associated "
1372+
"with any products). This query is only "
1373+
"available to SUPERUSERs.")
1374+
1375+
timestamp_documentation: str = """
1376+
TIMESTAMP, which is given in the format of 'year:month:day' or
1377+
'year:month:day:hour:minute:second'.
1378+
If the "time" part (':hour:minute:second') is not given, 00:00:00 (midnight)
1379+
is assumed instead.
1380+
Timestamps for tasks are always understood as Coordinated Universal Time (UTC).
1381+
"""
1382+
1383+
task_list.add_argument("--enqueued-before",
1384+
type=valid_time,
1385+
metavar="TIMESTAMP",
1386+
help="Filter for tasks that were created BEFORE "
1387+
"(or on) the specified " +
1388+
timestamp_documentation)
1389+
task_list.add_argument("--enqueued-after",
1390+
type=valid_time,
1391+
metavar="TIMESTAMP",
1392+
help="Filter for tasks that were created AFTER "
1393+
"(or on) the specified " +
1394+
timestamp_documentation)
1395+
1396+
task_list.add_argument("--started-before",
1397+
type=valid_time,
1398+
metavar="TIMESTAMP",
1399+
help="Filter for tasks that were started "
1400+
"execution BEFORE (or on) the specified " +
1401+
timestamp_documentation)
1402+
task_list.add_argument("--started-after",
1403+
type=valid_time,
1404+
metavar="TIMESTAMP",
1405+
help="Filter for tasks that were started "
1406+
"execution AFTER (or on) the specified " +
1407+
timestamp_documentation)
1408+
1409+
task_list.add_argument("--finished-before",
1410+
type=valid_time,
1411+
metavar="TIMESTAMP",
1412+
help="Filter for tasks that concluded execution "
1413+
"BEFORE (or on) the specified " +
1414+
timestamp_documentation)
1415+
task_list.add_argument("--finished-after",
1416+
type=valid_time,
1417+
metavar="TIMESTAMP",
1418+
help="Filter for tasks that concluded execution "
1419+
"execution AFTER (or on) the specified " +
1420+
timestamp_documentation)
1421+
1422+
task_list.add_argument("--last-seen-before",
1423+
type=valid_time,
1424+
metavar="TIMESTAMP",
1425+
help="Filter for tasks that reported actual "
1426+
"forward progress in its execution "
1427+
"(\"heartbeat\") BEFORE (or on) the "
1428+
"specified " + timestamp_documentation)
1429+
task_list.add_argument("--last-seen-after",
1430+
type=valid_time,
1431+
metavar="TIMESTAMP",
1432+
help="Filter for tasks that reported actual "
1433+
"forward progress in its execution "
1434+
"(\"heartbeat\") AFTER (or on) the "
1435+
"specified " + timestamp_documentation)
1436+
1437+
cancel = task_list.add_mutually_exclusive_group(required=False)
1438+
cancel.add_argument("--only-cancelled",
1439+
action="store_true",
1440+
help="Show only tasks that received a cancel request "
1441+
"from a SUPERUSER (see '--kill').")
1442+
cancel.add_argument("--no-cancelled",
1443+
action="store_true",
1444+
help="Show only tasks that had not received a "
1445+
"cancel request from a SUPERUSER "
1446+
"(see '--kill').")
1447+
1448+
consumed = task_list.add_mutually_exclusive_group(required=False)
1449+
consumed.add_argument("--only-consumed",
1450+
action="store_true",
1451+
help="Show only tasks that concluded their "
1452+
"execution and the responsible user (see "
1453+
"'--username') \"downloaded\" this fact.")
1454+
consumed.add_argument("--no-consumed",
1455+
action="store_true",
1456+
help="Show only tasks that concluded their "
1457+
"execution but the responsible user (see "
1458+
"'--username') did not \"check\" on the task.")
1459+
1460+
12321461
def __register_token(parser):
12331462
"""
12341463
Add argparse subcommand parser for the "handle token" action.
@@ -1552,5 +1781,41 @@ def add_arguments_to_parser(parser):
15521781
permissions.set_defaults(func=permission_client.handle_permissions)
15531782
__add_common_arguments(permissions, needs_product_url=False)
15541783

1784+
tasks = subcommands.add_parser(
1785+
"serverside-tasks",
1786+
formatter_class=arg.RawDescriptionDefaultHelpFormatter,
1787+
description="""
1788+
Query the status of and otherwise filter information for server-side
1789+
background tasks executing on a CodeChecker server. In addition, for server
1790+
administartors, allows requesting tasks to cancel execution.
1791+
1792+
Normally, the querying of a task's status is available only to the following
1793+
users:
1794+
- The user who caused the creation of the task.
1795+
- For tasks that are associated with a specific product, the PRODUCT_ADMIN
1796+
users of that product.
1797+
- Accounts with SUPERUSER rights (server administrators).
1798+
""",
1799+
help="Await, query, and cancel background tasks executing on the "
1800+
"server.",
1801+
epilog="""
1802+
The return code of 'CodeChecker cmd serverside-tasks' is almost always '0',
1803+
unless there is an error.
1804+
If **EXACTLY** one '--token' is specified in the arguments without the use of
1805+
'--await' or '--kill', the return code is based on the current status of the
1806+
task, as identified by the token:
1807+
- 0: The task completed successfully.
1808+
- 1: (Reserved for operational errors.)
1809+
- 2: (Reserved for command-line errors.)
1810+
- 4: The task failed to complete due to an error during execution.
1811+
- 8: The task is still running...
1812+
- 16: The task was cancelled by the administrators, or the server was shut
1813+
down.
1814+
"""
1815+
)
1816+
__register_tasks(tasks)
1817+
tasks.set_defaults(func=task_client.handle_tasks)
1818+
__add_common_arguments(tasks, needs_product_url=False)
1819+
15551820
# 'cmd' does not have a main() method in itself, as individual subcommands are
15561821
# handled later on separately.

0 commit comments

Comments
 (0)