argus-alm 0.12.2__py3-none-any.whl → 0.12.4b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- argus/backend/cli.py +1 -1
- argus/backend/controller/admin_api.py +26 -0
- argus/backend/controller/api.py +26 -1
- argus/backend/controller/main.py +21 -0
- argus/backend/controller/testrun_api.py +132 -1
- argus/backend/controller/view_api.py +162 -0
- argus/backend/models/web.py +16 -0
- argus/backend/plugins/core.py +28 -5
- argus/backend/plugins/driver_matrix_tests/controller.py +39 -0
- argus/backend/plugins/driver_matrix_tests/model.py +252 -4
- argus/backend/plugins/driver_matrix_tests/raw_types.py +27 -0
- argus/backend/plugins/driver_matrix_tests/service.py +18 -0
- argus/backend/plugins/driver_matrix_tests/udt.py +14 -13
- argus/backend/plugins/generic/model.py +6 -3
- argus/backend/plugins/loader.py +2 -2
- argus/backend/plugins/sct/controller.py +31 -0
- argus/backend/plugins/sct/plugin.py +2 -1
- argus/backend/plugins/sct/service.py +101 -3
- argus/backend/plugins/sct/testrun.py +8 -2
- argus/backend/plugins/sct/types.py +18 -0
- argus/backend/plugins/sct/udt.py +6 -0
- argus/backend/plugins/sirenada/model.py +1 -1
- argus/backend/service/argus_service.py +116 -11
- argus/backend/service/build_system_monitor.py +37 -7
- argus/backend/service/jenkins_service.py +176 -1
- argus/backend/service/release_manager.py +14 -0
- argus/backend/service/stats.py +179 -21
- argus/backend/service/testrun.py +44 -5
- argus/backend/service/views.py +258 -0
- argus/backend/template_filters.py +7 -0
- argus/backend/util/common.py +14 -2
- argus/client/driver_matrix_tests/cli.py +110 -0
- argus/client/driver_matrix_tests/client.py +56 -193
- argus/client/sct/client.py +34 -0
- argus_alm-0.12.4b1.dist-info/METADATA +129 -0
- {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/RECORD +39 -36
- {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/WHEEL +1 -1
- {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/entry_points.txt +1 -0
- argus_alm-0.12.2.dist-info/METADATA +0 -206
- {argus_alm-0.12.2.dist-info → argus_alm-0.12.4b1.dist-info}/LICENSE +0 -0
argus/backend/util/common.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from itertools import islice
|
|
1
2
|
import logging
|
|
2
|
-
from typing import Callable
|
|
3
|
+
from typing import Callable, Iterable
|
|
3
4
|
from uuid import UUID
|
|
4
5
|
|
|
5
|
-
from flask import Request, Response
|
|
6
|
+
from flask import Request, Response, g
|
|
7
|
+
|
|
8
|
+
from argus.backend.models.web import User
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -20,6 +23,11 @@ def first(iterable, value, key: Callable = None, predicate: Callable = None):
|
|
|
20
23
|
return None
|
|
21
24
|
|
|
22
25
|
|
|
26
|
+
def chunk(iterable: Iterable, slice_size = 90):
|
|
27
|
+
it = iter(iterable)
|
|
28
|
+
return iter(lambda: list(islice(it, slice_size)), [])
|
|
29
|
+
|
|
30
|
+
|
|
23
31
|
def check_scheduled_test(test, group, testname):
|
|
24
32
|
return testname in (f"{group}/{test}", test)
|
|
25
33
|
|
|
@@ -43,6 +51,10 @@ def get_payload(client_request: Request) -> dict:
|
|
|
43
51
|
return request_payload
|
|
44
52
|
|
|
45
53
|
|
|
54
|
+
def current_user() -> User:
|
|
55
|
+
return g.user
|
|
56
|
+
|
|
57
|
+
|
|
46
58
|
def get_build_number(build_job_url: str) -> int | None:
|
|
47
59
|
build_number = build_job_url.rstrip("/").split("/")[-1] if build_job_url else -1
|
|
48
60
|
if build_number:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import click
|
|
5
|
+
import logging
|
|
6
|
+
from argus.backend.util.enums import TestStatus
|
|
7
|
+
|
|
8
|
+
from argus.client.driver_matrix_tests.client import ArgusDriverMatrixClient
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group
|
|
14
|
+
def cli():
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _submit_driver_result_internal(api_key: str, base_url: str, run_id: str, metadata_path: str):
|
|
19
|
+
metadata = json.loads(Path(metadata_path).read_text(encoding="utf-8"))
|
|
20
|
+
LOGGER.info("Submitting results for %s [%s/%s] to Argus...", run_id, metadata["driver_name"], metadata["driver_type"])
|
|
21
|
+
raw_xml = Path(metadata["junit_result"]).read_bytes()
|
|
22
|
+
client = ArgusDriverMatrixClient(run_id=run_id, auth_token=api_key, base_url=base_url)
|
|
23
|
+
client.submit_driver_result(driver_name=metadata["driver_name"], driver_type=metadata["driver_type"], raw_junit_data=base64.encodebytes(raw_xml))
|
|
24
|
+
LOGGER.info("Done.")
|
|
25
|
+
|
|
26
|
+
def _submit_driver_failure_internal(api_key: str, base_url: str, run_id: str, metadata_path: str):
|
|
27
|
+
metadata = json.loads(Path(metadata_path).read_text(encoding="utf-8"))
|
|
28
|
+
LOGGER.info("Submitting failure for %s [%s/%s] to Argus...", run_id, metadata["driver_name"], metadata["driver_type"])
|
|
29
|
+
client = ArgusDriverMatrixClient(run_id=run_id, auth_token=api_key, base_url=base_url)
|
|
30
|
+
client.submit_driver_failure(driver_name=metadata["driver_name"], driver_type=metadata["driver_type"], failure_reason=metadata["failure_reason"])
|
|
31
|
+
LOGGER.info("Done.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.command("submit-run")
|
|
35
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
36
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
37
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
38
|
+
@click.option("--build-id", required=True, help="Unique job identifier in the build system, e.g. scylla-master/group/job for jenkins (The full path)")
|
|
39
|
+
@click.option("--build-url", required=True, help="Job URL in the build system")
|
|
40
|
+
def submit_driver_matrix_run(api_key: str, base_url: str, run_id: str, build_id: str, build_url: str):
|
|
41
|
+
LOGGER.info("Submitting %s (%s) to Argus...", build_id, run_id)
|
|
42
|
+
client = ArgusDriverMatrixClient(run_id=run_id, auth_token=api_key, base_url=base_url)
|
|
43
|
+
client.submit_driver_matrix_run(job_name=build_id, job_url=build_url)
|
|
44
|
+
LOGGER.info("Done.")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.command("submit-driver")
|
|
48
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
49
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
50
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
51
|
+
@click.option("--metadata-path", required=True, help="Path to the metadata .json file that contains path to junit xml and other required information")
|
|
52
|
+
def submit_driver_result(api_key: str, base_url: str, run_id: str, metadata_path: str):
|
|
53
|
+
_submit_driver_result_internal(api_key=api_key, base_url=base_url, run_id=run_id, metadata_path=metadata_path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@click.command("fail-driver")
|
|
57
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
58
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
59
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
60
|
+
@click.option("--metadata-path", required=True, help="Path to the metadata .json file that contains path to junit xml and other required information")
|
|
61
|
+
def submit_driver_failure(api_key: str, base_url: str, run_id: str, metadata_path: str):
|
|
62
|
+
_submit_driver_failure_internal(api_key=api_key, base_url=base_url, run_id=run_id, metadata_path=metadata_path)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@click.command("submit-or-fail-driver")
|
|
66
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
67
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
68
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
69
|
+
@click.option("--metadata-path", required=True, help="Path to the metadata .json file that contains path to junit xml and other required information")
|
|
70
|
+
def submit_or_fail_driver(api_key: str, base_url: str, run_id: str, metadata_path: str):
|
|
71
|
+
metadata = json.loads(Path(metadata_path).read_text(encoding="utf-8"))
|
|
72
|
+
if metadata.get("failure_reason"):
|
|
73
|
+
_submit_driver_failure_internal(api_key=api_key, base_url=base_url, run_id=run_id, metadata_path=metadata_path)
|
|
74
|
+
else:
|
|
75
|
+
_submit_driver_result_internal(api_key=api_key, base_url=base_url, run_id=run_id, metadata_path=metadata_path)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@click.command("submit-env")
|
|
79
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
80
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
81
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
82
|
+
@click.option("--env-path", required=True, help="Path to the Build-00.txt file that contains environment information about Scylla")
|
|
83
|
+
def submit_driver_env(api_key: str, base_url: str, run_id: str, env_path: str):
|
|
84
|
+
LOGGER.info("Submitting environment for run %s to Argus...", run_id)
|
|
85
|
+
raw_env = Path(env_path).read_text()
|
|
86
|
+
client = ArgusDriverMatrixClient(run_id=run_id, auth_token=api_key, base_url=base_url)
|
|
87
|
+
client.submit_env(raw_env)
|
|
88
|
+
LOGGER.info("Done.")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@click.command("finish-run")
|
|
92
|
+
@click.option("--api-key", help="Argus API key for authorization", required=True)
|
|
93
|
+
@click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
|
|
94
|
+
@click.option("--id", "run_id", required=True, help="UUID (v4 or v1) unique to the job")
|
|
95
|
+
@click.option("--status", required=True, help="Resulting job status")
|
|
96
|
+
def finish_driver_matrix_run(api_key: str, base_url: str, run_id: str, status: str):
|
|
97
|
+
client = ArgusDriverMatrixClient(run_id=run_id, auth_token=api_key, base_url=base_url)
|
|
98
|
+
client.finalize_run(run_type=ArgusDriverMatrixClient.test_type, run_id=run_id, body={"status": TestStatus(status)})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
cli.add_command(submit_driver_matrix_run)
|
|
102
|
+
cli.add_command(submit_driver_result)
|
|
103
|
+
cli.add_command(submit_or_fail_driver)
|
|
104
|
+
cli.add_command(submit_driver_failure)
|
|
105
|
+
cli.add_command(submit_driver_env)
|
|
106
|
+
cli.add_command(finish_driver_matrix_run)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
cli()
|
|
@@ -1,216 +1,79 @@
|
|
|
1
|
-
from functools import reduce
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import TypedDict, Literal
|
|
4
|
-
import re
|
|
5
|
-
import logging
|
|
6
1
|
from uuid import UUID
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from pprint import pformat
|
|
9
|
-
from glob import glob
|
|
10
|
-
from xml.etree import ElementTree
|
|
11
|
-
|
|
12
2
|
from argus.backend.util.enums import TestStatus
|
|
13
3
|
from argus.client.base import ArgusClient
|
|
14
|
-
from argus.backend.plugins.driver_matrix_tests.raw_types import RawMatrixTestResult, RawMatrixTestCase
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
LOGGER = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
TestTypeType = Literal['java', 'cpp', 'python', 'gocql']
|
|
20
|
-
|
|
21
|
-
class AdaptedXUnitData(TypedDict):
|
|
22
|
-
timestamp: str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def python_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
26
|
-
testsuites = list(xml.getroot().iter("testsuite"))
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
"timestamp": testsuites[0].attrib.get("timestamp"),
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def java_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
34
|
-
testsuites = xml.getroot()
|
|
35
|
-
ts_now = datetime.utcnow().timestamp()
|
|
36
|
-
try:
|
|
37
|
-
time_taken = float(testsuites.attrib.get("time"))
|
|
38
|
-
except ValueError:
|
|
39
|
-
time_taken = 0.0
|
|
40
|
-
|
|
41
|
-
timestamp = datetime.utcfromtimestamp(ts_now - time_taken).isoformat()
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
"timestamp": timestamp,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def cpp_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
49
|
-
testsuites = xml.getroot()
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
"timestamp": testsuites.attrib.get("timestamp"),
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def gocql_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
57
|
-
testsuites = list(xml.getroot().iter("testsuite"))
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
"timestamp": testsuites[0].attrib.get("timestamp"),
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def generic_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
|
|
65
|
-
return {
|
|
66
|
-
"timestamp": datetime.utcnow().isoformat()
|
|
67
|
-
}
|
|
68
4
|
|
|
69
5
|
|
|
70
6
|
class ArgusDriverMatrixClient(ArgusClient):
|
|
71
7
|
test_type = "driver-matrix-tests"
|
|
72
|
-
schema_version: None = "
|
|
8
|
+
schema_version: None = "v2"
|
|
73
9
|
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"gocql": gocql_driver_matrix_adapter,
|
|
79
|
-
}
|
|
10
|
+
class Routes(ArgusClient.Routes):
|
|
11
|
+
SUBMIT_DRIVER_RESULT = "/driver_matrix/result/submit"
|
|
12
|
+
SUBMIT_DRIVER_FAILURE = "/driver_matrix/result/fail"
|
|
13
|
+
SUBMIT_ENV = "/driver_matrix/env/submit"
|
|
80
14
|
|
|
81
15
|
def __init__(self, run_id: UUID, auth_token: str, base_url: str, api_version="v1") -> None:
|
|
82
16
|
super().__init__(auth_token, base_url, api_version)
|
|
83
17
|
self.run_id = run_id
|
|
84
18
|
|
|
85
|
-
def
|
|
86
|
-
with open(Path(env_path), mode="rt", encoding="utf-8") as env_file:
|
|
87
|
-
raw_env = env_file.read()
|
|
88
|
-
|
|
89
|
-
result = {}
|
|
90
|
-
for line in raw_env.split("\n"):
|
|
91
|
-
if not line:
|
|
92
|
-
continue
|
|
93
|
-
LOGGER.debug("ENV: %s", line)
|
|
94
|
-
key, val = line.split(": ")
|
|
95
|
-
result[key] = val.strip()
|
|
96
|
-
|
|
97
|
-
return result
|
|
98
|
-
|
|
99
|
-
def get_test_cases(self, cases: list[ElementTree.Element]) -> list[RawMatrixTestCase]:
|
|
100
|
-
raw_cases = []
|
|
101
|
-
for case in cases:
|
|
102
|
-
children = list(case.findall("./*"))
|
|
103
|
-
if len(children) > 0:
|
|
104
|
-
status = children[0].tag
|
|
105
|
-
message = f"{children[0].attrib.get('message', 'no-message')} ({children[0].attrib.get('type', 'no-type')})"
|
|
106
|
-
else:
|
|
107
|
-
status = "passed"
|
|
108
|
-
message = ""
|
|
109
|
-
|
|
110
|
-
raw_cases.append({
|
|
111
|
-
"name": case.attrib["name"],
|
|
112
|
-
"status": status,
|
|
113
|
-
"time": float(case.attrib.get("time", 0.0)),
|
|
114
|
-
"classname": case.attrib.get("classname", ""),
|
|
115
|
-
"message": message,
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
return raw_cases
|
|
119
|
-
|
|
120
|
-
def get_driver_info(self, xml_name: str, test_type: TestTypeType) -> dict[str, str]:
|
|
121
|
-
if test_type == "cpp":
|
|
122
|
-
filename_re = r"TEST-(?P<driver_name>[\w]*)-(?P<version>[\d\.]*)\.xml"
|
|
123
|
-
else:
|
|
124
|
-
filename_re = r"(?P<name>[\w]*)\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[\d\.]*)\.xml"
|
|
125
|
-
|
|
126
|
-
match = re.match(filename_re, xml_name)
|
|
127
|
-
|
|
128
|
-
return match.groupdict() if match else {}
|
|
129
|
-
|
|
130
|
-
def get_passed_count(self, suite_attribs: dict[str, str]) -> int:
|
|
131
|
-
if (pass_count := suite_attribs.get("passed")):
|
|
132
|
-
return int(pass_count)
|
|
133
|
-
total = int(suite_attribs.get("tests", 0))
|
|
134
|
-
errors = int(suite_attribs.get("errors", 0))
|
|
135
|
-
skipped = int(suite_attribs.get("skipped", 0))
|
|
136
|
-
failures = int(suite_attribs.get("failures", 0))
|
|
137
|
-
|
|
138
|
-
return total - errors - skipped - failures
|
|
139
|
-
|
|
140
|
-
def parse_result_xml(self, xml_path: Path, test_type: TestTypeType) -> RawMatrixTestResult:
|
|
141
|
-
with xml_path.open(mode="rt", encoding="utf-8") as xml_file:
|
|
142
|
-
xml = ElementTree.parse(source=xml_file)
|
|
143
|
-
LOGGER.info("%s", pformat(xml))
|
|
144
|
-
testsuites = xml.getroot()
|
|
145
|
-
adapted_data = self.TEST_ADAPTERS.get(test_type, generic_adapter)(xml)
|
|
146
|
-
|
|
147
|
-
driver_info = self.get_driver_info(xml_path.name, test_type)
|
|
148
|
-
test_collection = {
|
|
149
|
-
"timestamp": adapted_data["timestamp"],
|
|
150
|
-
"name": xml_path.stem,
|
|
151
|
-
"driver_name": driver_info.get("driver_name"),
|
|
152
|
-
"tests": 0,
|
|
153
|
-
"failures": 0,
|
|
154
|
-
"errors": 0,
|
|
155
|
-
"disabled": 0,
|
|
156
|
-
"skipped": 0,
|
|
157
|
-
"passed": 0,
|
|
158
|
-
"time": 0.0,
|
|
159
|
-
}
|
|
160
|
-
all_suites = []
|
|
161
|
-
for suite in testsuites.iter("testsuite"):
|
|
162
|
-
raw_suite = {
|
|
163
|
-
"name": suite.attrib["name"],
|
|
164
|
-
"tests": int(suite.attrib.get("tests", 0)),
|
|
165
|
-
"failures": int(suite.attrib.get("failures", 0)),
|
|
166
|
-
"disabled": int(0),
|
|
167
|
-
"passed": self.get_passed_count(suite.attrib),
|
|
168
|
-
"skipped": int(suite.attrib.get("skipped", 0)),
|
|
169
|
-
"errors": int(suite.attrib.get("errors", 0)),
|
|
170
|
-
"time": float(suite.attrib["time"]),
|
|
171
|
-
"cases": self.get_test_cases(list(suite.iter("testcase")))
|
|
172
|
-
}
|
|
173
|
-
all_suites.append(raw_suite)
|
|
174
|
-
test_collection["tests"] += raw_suite["tests"]
|
|
175
|
-
test_collection["failures"] += raw_suite["failures"]
|
|
176
|
-
test_collection["errors"] += raw_suite["errors"]
|
|
177
|
-
test_collection["disabled"] += raw_suite["disabled"]
|
|
178
|
-
test_collection["skipped"] += raw_suite["skipped"]
|
|
179
|
-
test_collection["passed"] += raw_suite["passed"]
|
|
180
|
-
test_collection["time"] += raw_suite["time"]
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
**test_collection,
|
|
184
|
-
"suites": all_suites
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
def get_results(self, result_path: str, test_type: TestTypeType) -> list[RawMatrixTestResult]:
|
|
188
|
-
xmls = glob(f"{result_path}/**/*.xml", recursive=True)
|
|
189
|
-
LOGGER.info("Will use following XMLs: %s", pformat(xmls))
|
|
190
|
-
results = []
|
|
191
|
-
for xml in xmls:
|
|
192
|
-
result = self.parse_result_xml(Path(xml), test_type)
|
|
193
|
-
results.append(result)
|
|
194
|
-
return results
|
|
195
|
-
|
|
196
|
-
def submit(self, build_id: str, build_url: str, env_path: str, result_path: str, test_type: TestTypeType):
|
|
197
|
-
env = self.parse_build_environment(env_path)
|
|
198
|
-
results = self.get_results(result_path, test_type)
|
|
199
|
-
|
|
200
|
-
self.submit_driver_matrix_run(job_name=build_id, job_url=build_url, test_environment=env, results=results)
|
|
201
|
-
|
|
202
|
-
def submit_driver_matrix_run(self, job_name: str, job_url: str,
|
|
203
|
-
results: list[RawMatrixTestResult], test_environment: dict[str, str]) -> None:
|
|
19
|
+
def submit_driver_matrix_run(self, job_name: str, job_url: str) -> None:
|
|
204
20
|
response = super().submit_run(run_type=self.test_type, run_body={
|
|
205
21
|
"run_id": str(self.run_id),
|
|
206
22
|
"job_name": job_name,
|
|
207
|
-
"job_url": job_url
|
|
208
|
-
"test_environment": test_environment,
|
|
209
|
-
"matrix_results": results
|
|
23
|
+
"job_url": job_url
|
|
210
24
|
})
|
|
211
25
|
|
|
212
26
|
self.check_response(response)
|
|
213
27
|
|
|
28
|
+
def submit_driver_result(self, driver_name: str, driver_type: str, raw_junit_data: bytes):
|
|
29
|
+
"""
|
|
30
|
+
Submit results of a single driver run
|
|
31
|
+
"""
|
|
32
|
+
response = self.post(
|
|
33
|
+
endpoint=self.Routes.SUBMIT_DRIVER_RESULT,
|
|
34
|
+
location_params={},
|
|
35
|
+
body={
|
|
36
|
+
**self.generic_body,
|
|
37
|
+
"run_id": str(self.run_id),
|
|
38
|
+
"driver_name": driver_name,
|
|
39
|
+
"driver_type": driver_type,
|
|
40
|
+
"raw_xml": str(raw_junit_data, encoding="utf-8"),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
self.check_response(response)
|
|
44
|
+
|
|
45
|
+
def submit_driver_failure(self, driver_name: str, driver_type: str, failure_reason: str):
|
|
46
|
+
"""
|
|
47
|
+
Submit a failure to run of a specific driver test
|
|
48
|
+
"""
|
|
49
|
+
response = self.post(
|
|
50
|
+
endpoint=self.Routes.SUBMIT_DRIVER_FAILURE,
|
|
51
|
+
location_params={},
|
|
52
|
+
body={
|
|
53
|
+
**self.generic_body,
|
|
54
|
+
"run_id": str(self.run_id),
|
|
55
|
+
"driver_name": driver_name,
|
|
56
|
+
"driver_type": driver_type,
|
|
57
|
+
"failure_reason": failure_reason,
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
self.check_response(response)
|
|
61
|
+
|
|
62
|
+
def submit_env(self, env_info: str):
|
|
63
|
+
"""
|
|
64
|
+
Submit env-info file (Build-00.txt)
|
|
65
|
+
"""
|
|
66
|
+
response = self.post(
|
|
67
|
+
endpoint=self.Routes.SUBMIT_ENV,
|
|
68
|
+
location_params={},
|
|
69
|
+
body={
|
|
70
|
+
**self.generic_body,
|
|
71
|
+
"run_id": str(self.run_id),
|
|
72
|
+
"raw_env": env_info,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
self.check_response(response)
|
|
76
|
+
|
|
214
77
|
def set_matrix_status(self, status: TestStatus):
|
|
215
78
|
response = super().set_status(run_type=self.test_type, run_id=self.run_id, new_status=status)
|
|
216
79
|
self.check_response(response)
|
argus/client/sct/client.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from typing import Any
|
|
1
3
|
from uuid import UUID
|
|
2
4
|
from dataclasses import asdict
|
|
3
5
|
from argus.backend.plugins.sct.types import GeminiResultsRequest, PerformanceResultsRequest
|
|
@@ -15,6 +17,7 @@ class ArgusSCTClient(ArgusClient):
|
|
|
15
17
|
SUBMIT_SCREENSHOTS = "/sct/$id/screenshots/submit"
|
|
16
18
|
CREATE_RESOURCE = "/sct/$id/resource/create"
|
|
17
19
|
TERMINATE_RESOURCE = "/sct/$id/resource/$name/terminate"
|
|
20
|
+
UPDATE_RESOURCE = "/sct/$id/resource/$name/update"
|
|
18
21
|
SET_SCT_RUNNER = "/sct/$id/sct_runner/set"
|
|
19
22
|
UPDATE_SHARDS_FOR_RESOURCE = "/sct/$id/resource/$name/shards"
|
|
20
23
|
SUBMIT_NEMESIS = "/sct/$id/nemesis/submit"
|
|
@@ -22,6 +25,7 @@ class ArgusSCTClient(ArgusClient):
|
|
|
22
25
|
SUBMIT_PERFORMANCE_RESULTS = "/sct/$id/performance/submit"
|
|
23
26
|
FINALIZE_NEMESIS = "/sct/$id/nemesis/finalize"
|
|
24
27
|
SUBMIT_EVENTS = "/sct/$id/events/submit"
|
|
28
|
+
SUBMIT_JUNIT_REPORT = "/sct/$id/junit/submit"
|
|
25
29
|
|
|
26
30
|
def __init__(self, run_id: UUID, auth_token: str, base_url: str, api_version="v1") -> None:
|
|
27
31
|
super().__init__(auth_token, base_url, api_version)
|
|
@@ -204,6 +208,22 @@ class ArgusSCTClient(ArgusClient):
|
|
|
204
208
|
)
|
|
205
209
|
self.check_response(response)
|
|
206
210
|
|
|
211
|
+
|
|
212
|
+
def update_resource(self, name: str, update_data: dict[str, Any]) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Update fields of the resource.
|
|
215
|
+
"""
|
|
216
|
+
response = self.post(
|
|
217
|
+
endpoint=self.Routes.UPDATE_RESOURCE,
|
|
218
|
+
location_params={"id": str(self.run_id), "name": name},
|
|
219
|
+
body={
|
|
220
|
+
**self.generic_body,
|
|
221
|
+
"update_data": update_data,
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
self.check_response(response)
|
|
225
|
+
|
|
226
|
+
|
|
207
227
|
def submit_nemesis(self, name: str, class_name: str, start_time: int,
|
|
208
228
|
target_name: str, target_ip: str, target_shards: int) -> None:
|
|
209
229
|
"""
|
|
@@ -265,3 +285,17 @@ class ArgusSCTClient(ArgusClient):
|
|
|
265
285
|
Updates a heartbeat field for an already existing test.
|
|
266
286
|
"""
|
|
267
287
|
super().heartbeat(run_type=self.test_type, run_id=self.run_id)
|
|
288
|
+
|
|
289
|
+
def sct_submit_junit_report(self, file_name: str, raw_content: str) -> None:
|
|
290
|
+
"""
|
|
291
|
+
Submits a JUnit-formatted XML report to argus
|
|
292
|
+
"""
|
|
293
|
+
response = self.post(
|
|
294
|
+
endpoint=self.Routes.SUBMIT_JUNIT_REPORT,
|
|
295
|
+
location_params={"id": str(self.run_id)},
|
|
296
|
+
body={
|
|
297
|
+
**self.generic_body,
|
|
298
|
+
"file_name": file_name,
|
|
299
|
+
"content": str(base64.encodebytes(bytes(raw_content, encoding="utf-8")), encoding="utf-8")
|
|
300
|
+
}
|
|
301
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: argus-alm
|
|
3
|
+
Version: 0.12.4b1
|
|
4
|
+
Summary: Argus
|
|
5
|
+
Home-page: https://github.com/scylladb/argus
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Alexey Kartashov
|
|
8
|
+
Author-email: alexey.kartashov@scylladb.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
16
|
+
Requires-Dist: requests (>=2.26.0,<3.0.0)
|
|
17
|
+
Project-URL: Repository, https://github.com/scylladb/argus
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Argus
|
|
21
|
+
|
|
22
|
+
## Description
|
|
23
|
+
|
|
24
|
+
Argus is a test tracking system intended to provide observability into automated test pipelines which use long-running resources. It allows observation of a test status, its events and its allocated resources. It also allows easy comparison between particular runs of a specific test.
|
|
25
|
+
|
|
26
|
+
## Installation notes
|
|
27
|
+
|
|
28
|
+
### Development
|
|
29
|
+
|
|
30
|
+
For development setup instructions, see [dev-setup.md](./docs/dev-setup.md).
|
|
31
|
+
|
|
32
|
+
### Prerequisites
|
|
33
|
+
|
|
34
|
+
- Python >=3.10.0 (system-wide or pyenv)
|
|
35
|
+
|
|
36
|
+
- NodeJS >=16 (with npm)
|
|
37
|
+
|
|
38
|
+
- Yarn (can be installed globally with `npm -g install yarn`)
|
|
39
|
+
|
|
40
|
+
- nginx
|
|
41
|
+
|
|
42
|
+
- poetry >=1.2.0b1
|
|
43
|
+
|
|
44
|
+
### From source
|
|
45
|
+
|
|
46
|
+
#### Production
|
|
47
|
+
|
|
48
|
+
Perform the following steps:
|
|
49
|
+
|
|
50
|
+
Create a user that will be used by uwsgi:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
useradd -m -s /bin/bash argus
|
|
54
|
+
sudo -iu argus
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
(Optional) Install pyenv and create a virtualenv for this user:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pyenv install 3.10.0
|
|
61
|
+
pyenv virtualenv argus
|
|
62
|
+
pyenv activate argus
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Clone the project into a directory somewhere where user has full write permissions
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/scylladb/argus ~/app
|
|
69
|
+
cd ~/app
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Install project dependencies:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
poetry install --with default,dev,web-backend,docker-image
|
|
76
|
+
yarn install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Compile frontend files from `/frontend` into `/public/dist`
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
yarn webpack
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Create a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cp argus_web.example.yaml argus_web.yaml
|
|
89
|
+
cp argus.yaml argus.local.yaml
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Open `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).
|
|
93
|
+
|
|
94
|
+
Open `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* variables with their respective values.
|
|
95
|
+
|
|
96
|
+
Copy nginx configuration file from `docs/configs/argus.nginx.conf` to nginx virtual hosts directory:
|
|
97
|
+
|
|
98
|
+
Ubuntu:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
sudo cp docs/configs/argus.nginx.conf /etc/nginx/sites-available/argus
|
|
102
|
+
sudo ln -s /etc/nginx/sites-enabled/argus /etc/nginx/sites-available/argus
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
RHEL/Centos/Alma/Fedora:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
sudo cp docs/configs/argus.nginx.conf /etc/nginx/conf.d/argus.conf
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Adjust the webhost settings in that file as necessary, particularly `listen` and `server_name` directives.
|
|
112
|
+
|
|
113
|
+
Copy systemd service file from `docs/config/argus.service` to `/etc/systemd/system` directory:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sudo cp docs/config/argus.service /etc/systemd/system
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Open it and adjust the path to the `start_argus.sh` script in the `ExecStart=` directive and the user/group, then reload systemd daemon configuration and enable (and optionally start) the service.
|
|
120
|
+
|
|
121
|
+
WARNING: `start_argus.sh` assumes pyenv is installed into `~/.pyenv`
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
sudo systemctl daemon-reload
|
|
125
|
+
sudo systemctl enable --now argus.service
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|