argus-alm 0.10.2__tar.gz → 0.11.0__tar.gz
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-alm-0.10.2 → argus_alm-0.11.0}/PKG-INFO +3 -4
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/core.py +18 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/testrun.py +1 -1
- argus_alm-0.11.0/argus/backend/plugins/sirenada/model.py +113 -0
- argus_alm-0.11.0/argus/backend/plugins/sirenada/plugin.py +17 -0
- argus_alm-0.11.0/argus/backend/plugins/sirenada/types.py +35 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/stats.py +26 -3
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/testrun.py +5 -5
- argus_alm-0.11.0/argus/client/sirenada/client.py +151 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/pyproject.toml +4 -6
- {argus-alm-0.10.2 → argus_alm-0.11.0}/setup.py +4 -4
- {argus-alm-0.10.2 → argus_alm-0.11.0}/LICENSE +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/README.md +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/.gitkeep +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/cli.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/admin.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/admin_api.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/api.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/auth.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/client_api.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/main.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/notification_api.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/notifications.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/team.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/team_ui.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/controller/testrun_api.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/db.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/error_handlers.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/events/event_processors.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/models/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/models/web.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/model.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/plugin.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/loader.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/controller.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/plugin.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/resource_setup.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/service.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/sct/udt.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/admin.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/argus_service.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/build_system_monitor.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/client_service.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/notification_manager.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/release_manager.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/team_manager_service.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/service/user.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/template_filters.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/common.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/config.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/encoders.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/enums.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/logsetup.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/module_loaders.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/util/send_email.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/client/__init__.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/client/base.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/client/driver_matrix_tests/client.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/client/sct/client.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/client/sct/types.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/.gitkeep +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/argus_json.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/cloud_types.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/config.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/db_types.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/interface.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/testrun.py +0 -0
- {argus-alm-0.10.2 → argus_alm-0.11.0}/argus/db/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: argus-alm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Argus
|
|
5
5
|
Home-page: https://github.com/scylladb/argus
|
|
6
6
|
License: Apache-2.0
|
|
@@ -10,9 +10,8 @@ Requires-Python: >=3.10,<4.0
|
|
|
10
10
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist: scylla-driver (>=3.24.8,<4.0.0)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Requires-Dist: requests (>=2.26.0,<3.0.0)
|
|
16
15
|
Project-URL: Repository, https://github.com/scylladb/argus
|
|
17
16
|
Description-Content-Type: text/markdown
|
|
18
17
|
|
|
@@ -122,6 +122,24 @@ class PluginModelBase(Model):
|
|
|
122
122
|
|
|
123
123
|
return list(rows)
|
|
124
124
|
|
|
125
|
+
@classmethod
|
|
126
|
+
def get_run_meta_by_build_id(cls, build_id: str, limit: int = 10):
|
|
127
|
+
cluster = ScyllaCluster.get()
|
|
128
|
+
query = cluster.prepare("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
|
|
129
|
+
f"assignee, end_time, investigation_status, heartbeat FROM {cls.table_name()} WHERE build_id = ? LIMIT ?")
|
|
130
|
+
rows = cluster.session.execute(query=query, parameters=(build_id, limit))
|
|
131
|
+
|
|
132
|
+
return list(rows)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def get_run_meta_by_run_id(cls, run_id: UUID | str):
|
|
136
|
+
cluster = ScyllaCluster.get()
|
|
137
|
+
query = cluster.prepare("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
|
|
138
|
+
f"assignee, end_time, investigation_status, heartbeat FROM {cls.table_name()} WHERE id = ?")
|
|
139
|
+
rows = cluster.session.execute(query=query, parameters=(run_id,))
|
|
140
|
+
|
|
141
|
+
return list(rows)
|
|
142
|
+
|
|
125
143
|
@classmethod
|
|
126
144
|
def load_test_run(cls, run_id: UUID) -> 'PluginModelBase':
|
|
127
145
|
raise NotImplementedError()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from uuid import UUID, uuid4
|
|
3
|
+
from cassandra.cqlengine import columns
|
|
4
|
+
from cassandra.cqlengine.usertype import UserType
|
|
5
|
+
from cassandra.cqlengine.models import Model
|
|
6
|
+
from argus.backend.db import ScyllaCluster
|
|
7
|
+
from argus.backend.models.web import ArgusRelease
|
|
8
|
+
from argus.backend.plugins.core import PluginModelBase
|
|
9
|
+
from argus.backend.plugins.sirenada.types import RawSirenadaRequest, SirenadaPluginException
|
|
10
|
+
from argus.backend.util.enums import TestStatus
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SirenadaTest(UserType):
|
|
14
|
+
test_name = columns.Text()
|
|
15
|
+
class_name = columns.Text()
|
|
16
|
+
file_name = columns.Text()
|
|
17
|
+
browser_type = columns.Text()
|
|
18
|
+
cluster_type = columns.Text()
|
|
19
|
+
status = columns.Text()
|
|
20
|
+
duration = columns.Float()
|
|
21
|
+
message = columns.Text()
|
|
22
|
+
start_time = columns.DateTime()
|
|
23
|
+
stack_trace = columns.Text()
|
|
24
|
+
screenshot_file = columns.Text()
|
|
25
|
+
s3_folder_id = columns.Text()
|
|
26
|
+
requests_file = columns.Text()
|
|
27
|
+
sirenada_test_id = columns.Text()
|
|
28
|
+
sirenada_user = columns.Text()
|
|
29
|
+
sirenada_password = columns.Text()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SirenadaRun(PluginModelBase):
|
|
34
|
+
_plugin_name = "sirenada"
|
|
35
|
+
__table_name__ = "sirenada_run"
|
|
36
|
+
logs = columns.Map(key_type=columns.Text(), value_type=columns.Text())
|
|
37
|
+
# TODO: Legacy field name, should be renamed to product_version and abstracted
|
|
38
|
+
scylla_version = columns.Text()
|
|
39
|
+
region = columns.Text()
|
|
40
|
+
sirenada_test_ids = columns.List(value_type=columns.Text())
|
|
41
|
+
s3_folder_ids = columns.List(value_type=columns.Tuple(columns.Text(), columns.Text()))
|
|
42
|
+
browsers = columns.List(value_type=columns.Text())
|
|
43
|
+
clusters = columns.List(value_type=columns.Text())
|
|
44
|
+
sct_test_id = columns.UUID()
|
|
45
|
+
results = columns.List(value_type=columns.UserDefinedType(user_type=SirenadaTest))
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def _stats_query(cls) -> str:
|
|
49
|
+
return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
|
|
50
|
+
f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE release_id = ?")
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def get_distinct_product_versions(cls, release: ArgusRelease, cluster: ScyllaCluster = None) -> list[str]:
|
|
54
|
+
if not cluster:
|
|
55
|
+
cluster = ScyllaCluster.get()
|
|
56
|
+
statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
|
|
57
|
+
rows = cluster.session.execute(query=statement, parameters=(release.id,))
|
|
58
|
+
unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
|
|
59
|
+
|
|
60
|
+
return sorted(list(unique_versions), reverse=True)
|
|
61
|
+
|
|
62
|
+
def submit_product_version(self, version: str):
|
|
63
|
+
self.scylla_version = version
|
|
64
|
+
|
|
65
|
+
def submit_logs(self, logs: dict[str, str]):
|
|
66
|
+
raise SirenadaPluginException("Log submission is not supported for Sirenada")
|
|
67
|
+
|
|
68
|
+
def finish_run(self):
|
|
69
|
+
raise SirenadaPluginException("Sirenada runs do not need finalization")
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def load_test_run(cls, run_id: UUID) -> 'SirenadaRun':
|
|
73
|
+
return cls.get(id=run_id)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def submit_run(cls, request_data: RawSirenadaRequest) -> 'SirenadaRun':
|
|
77
|
+
try:
|
|
78
|
+
run = cls.get(id=UUID(request_data["run_id"]))
|
|
79
|
+
except cls.DoesNotExist:
|
|
80
|
+
run = cls()
|
|
81
|
+
run.id = request_data["run_id"]
|
|
82
|
+
run.build_id = request_data["build_id"]
|
|
83
|
+
run.start_time = datetime.utcnow()
|
|
84
|
+
run.assign_categories()
|
|
85
|
+
run.build_job_url = request_data["build_job_url"]
|
|
86
|
+
run.region = request_data["region"]
|
|
87
|
+
run.status = TestStatus.PASSED.value
|
|
88
|
+
try:
|
|
89
|
+
run.assignee = run.get_scheduled_assignee()
|
|
90
|
+
except Model.DoesNotExist:
|
|
91
|
+
run.assignee = None
|
|
92
|
+
|
|
93
|
+
for raw_case in request_data["results"]:
|
|
94
|
+
case = SirenadaTest(**raw_case)
|
|
95
|
+
if case.status in ["failed", "error"] and run.status not in [TestStatus.FAILED.value, TestStatus.ABORTED.value]:
|
|
96
|
+
run.status = TestStatus.FAILED.value
|
|
97
|
+
run.results.append(case)
|
|
98
|
+
|
|
99
|
+
if case.sirenada_test_id not in run.sirenada_test_ids:
|
|
100
|
+
run.sirenada_test_ids.append(case.sirenada_test_id)
|
|
101
|
+
|
|
102
|
+
if case.browser_type not in run.browsers:
|
|
103
|
+
run.browsers.append(case.browser_type)
|
|
104
|
+
|
|
105
|
+
if case.cluster_type not in run.clusters:
|
|
106
|
+
run.clusters.append(case.cluster_type)
|
|
107
|
+
|
|
108
|
+
if (case.s3_folder_id, case.sirenada_test_id) not in run.s3_folder_ids and case.s3_folder_id:
|
|
109
|
+
run.s3_folder_ids.append((case.s3_folder_id, case.sirenada_test_id))
|
|
110
|
+
|
|
111
|
+
run.save()
|
|
112
|
+
|
|
113
|
+
return run
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from flask import Blueprint
|
|
2
|
+
|
|
3
|
+
from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
|
|
4
|
+
from argus.backend.plugins.sirenada.model import SirenadaRun, SirenadaTest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PluginInfo(PluginInfoBase):
|
|
8
|
+
# pylint: disable=too-few-public-methods
|
|
9
|
+
name: str = "sirenada"
|
|
10
|
+
model: PluginModelBase = SirenadaRun
|
|
11
|
+
controller: Blueprint = None
|
|
12
|
+
all_models = [
|
|
13
|
+
SirenadaRun
|
|
14
|
+
]
|
|
15
|
+
all_types = [
|
|
16
|
+
SirenadaTest
|
|
17
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RawSirenadaTestCase(TypedDict):
|
|
5
|
+
test_name: str
|
|
6
|
+
class_name: str
|
|
7
|
+
file_name: str
|
|
8
|
+
browser_type: str
|
|
9
|
+
cluster_type: str
|
|
10
|
+
status: str
|
|
11
|
+
duration: float
|
|
12
|
+
message: str
|
|
13
|
+
start_time: str
|
|
14
|
+
stack_trace: str
|
|
15
|
+
screenshot_file: str
|
|
16
|
+
s3_folder_id: str
|
|
17
|
+
requests_file: str
|
|
18
|
+
sirenada_test_id: str
|
|
19
|
+
sirenada_user: str
|
|
20
|
+
sirenada_password: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RawSirenadaRequest(TypedDict):
|
|
24
|
+
build_id: str
|
|
25
|
+
build_job_url: str
|
|
26
|
+
run_id: str # UUID
|
|
27
|
+
region: list[str]
|
|
28
|
+
browsers: list[str]
|
|
29
|
+
clusters: list[str]
|
|
30
|
+
sct_test_id: str
|
|
31
|
+
results: list[RawSirenadaTestCase]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SirenadaPluginException(Exception):
|
|
35
|
+
pass
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import TypedDict
|
|
5
5
|
from uuid import UUID
|
|
6
|
+
|
|
6
7
|
from argus.backend.plugins.loader import all_plugin_models
|
|
7
8
|
from argus.backend.util.common import get_build_number
|
|
8
9
|
from argus.backend.util.enums import TestStatus, TestInvestigationStatus
|
|
@@ -92,16 +93,28 @@ class ReleaseStats:
|
|
|
92
93
|
self.all_tests = []
|
|
93
94
|
|
|
94
95
|
def to_dict(self) -> dict:
|
|
96
|
+
converted_groups = {str(group.group.id): group.to_dict() for group in self.groups}
|
|
97
|
+
aggregated_investigation_status = {}
|
|
98
|
+
for group in converted_groups.values():
|
|
99
|
+
for investigation_status in TestInvestigationStatus:
|
|
100
|
+
current_status = aggregated_investigation_status.get(investigation_status.value, {})
|
|
101
|
+
result = {
|
|
102
|
+
status.value: current_status.get(status.value, 0) + group.get(investigation_status.value, {}).get(status, 0)
|
|
103
|
+
for status in TestStatus
|
|
104
|
+
}
|
|
105
|
+
aggregated_investigation_status[investigation_status.value] = result
|
|
106
|
+
|
|
95
107
|
return {
|
|
96
108
|
"release": dict(self.release.items()),
|
|
97
|
-
"groups":
|
|
109
|
+
"groups": converted_groups,
|
|
98
110
|
"total": self.total_tests,
|
|
99
111
|
**self.status_map,
|
|
100
112
|
"disabled": not self.release.enabled,
|
|
101
113
|
"perpetual": self.release.perpetual,
|
|
102
114
|
"lastStatus": self.last_investigation_status,
|
|
103
115
|
"lastInvestigationStatus": self.last_investigation_status,
|
|
104
|
-
"hasBugReport": self.has_bug_report
|
|
116
|
+
"hasBugReport": self.has_bug_report,
|
|
117
|
+
**aggregated_investigation_status
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
def collect(self, rows: list[TestRunStatRow], limited=False, force=False) -> None:
|
|
@@ -144,6 +157,15 @@ class GroupStats:
|
|
|
144
157
|
self.tests: list[TestStats] = []
|
|
145
158
|
|
|
146
159
|
def to_dict(self) -> dict:
|
|
160
|
+
converted_tests = {str(test.test.id): test.to_dict() for test in self.tests}
|
|
161
|
+
investigation_progress = {}
|
|
162
|
+
for test in converted_tests.values():
|
|
163
|
+
progress_for_status = investigation_progress.get(test["investigation_status"], {})
|
|
164
|
+
status_count = progress_for_status.get(test["status"], 0)
|
|
165
|
+
status_count += 1
|
|
166
|
+
progress_for_status[test["status"]] = status_count
|
|
167
|
+
investigation_progress[test["investigation_status"]] = progress_for_status
|
|
168
|
+
|
|
147
169
|
return {
|
|
148
170
|
"group": dict(self.group.items()),
|
|
149
171
|
"total": self.total_tests,
|
|
@@ -151,7 +173,8 @@ class GroupStats:
|
|
|
151
173
|
"lastStatus": self.last_status,
|
|
152
174
|
"lastInvestigationStatus": self.last_investigation_status,
|
|
153
175
|
"disabled": self.disabled,
|
|
154
|
-
"tests":
|
|
176
|
+
"tests": converted_tests,
|
|
177
|
+
**investigation_progress
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
def collect(self, limited=False):
|
|
@@ -68,14 +68,14 @@ class TestRunService:
|
|
|
68
68
|
if not plugin:
|
|
69
69
|
return []
|
|
70
70
|
|
|
71
|
-
last_runs: list[
|
|
72
|
-
last_runs_ids = [run
|
|
71
|
+
last_runs: list[dict] = plugin.model.get_run_meta_by_build_id(build_id=test.build_system_id, limit=limit)
|
|
72
|
+
last_runs_ids = [run["id"] for run in last_runs]
|
|
73
73
|
for added_run in additional_runs:
|
|
74
74
|
if added_run not in last_runs_ids:
|
|
75
|
-
last_runs.
|
|
75
|
+
last_runs.extend(plugin.model.get_run_meta_by_run_id(run_id=added_run))
|
|
76
76
|
|
|
77
77
|
for row in last_runs:
|
|
78
|
-
|
|
78
|
+
row["build_number"] = get_build_number(build_job_url=row["build_job_url"])
|
|
79
79
|
|
|
80
80
|
return last_runs
|
|
81
81
|
|
|
@@ -391,7 +391,7 @@ class TestRunService:
|
|
|
391
391
|
response = []
|
|
392
392
|
for issue in all_issues:
|
|
393
393
|
runs = runs_by_issue.get(issue, [])
|
|
394
|
-
runs.append(issue.run_id)
|
|
394
|
+
runs.append({"test_id": issue.test_id, "run_id": issue.run_id})
|
|
395
395
|
runs_by_issue[issue] = runs
|
|
396
396
|
|
|
397
397
|
for issue, runs in runs_by_issue.items():
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from glob import glob, escape
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TypedDict
|
|
6
|
+
from xml.etree import ElementTree
|
|
7
|
+
|
|
8
|
+
from argus.backend.plugins.sirenada.types import RawSirenadaRequest, RawSirenadaTestCase
|
|
9
|
+
|
|
10
|
+
from argus.client.base import ArgusClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class SirenadaEnv(TypedDict):
|
|
16
|
+
SIRENADA_JOB_ID: str
|
|
17
|
+
SIRENADA_BROWSER: str
|
|
18
|
+
SIRENADA_REGION: str
|
|
19
|
+
SIRENADA_CLUSTER: str
|
|
20
|
+
WORKSPACE: str
|
|
21
|
+
BUILD_NUMBER: str
|
|
22
|
+
BUILD_URL: str
|
|
23
|
+
JOB_NAME: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestCredentials(TypedDict):
|
|
27
|
+
SIRENADA_TEST_ID: str
|
|
28
|
+
SIRENADA_USER_NAME: str
|
|
29
|
+
SIRENADA_USER_PASS: str
|
|
30
|
+
SIRENADA_OTP_SECRET: str
|
|
31
|
+
ClusterID: str
|
|
32
|
+
region: str
|
|
33
|
+
|
|
34
|
+
class ArgusSirenadaClient(ArgusClient):
|
|
35
|
+
test_type = "sirenada"
|
|
36
|
+
schema_version: None = "v1"
|
|
37
|
+
_junit_xml_filename = "junit_results.xml"
|
|
38
|
+
_credentials_filename = "test_credentials.json"
|
|
39
|
+
_bucket_url_template = "https://sirenada-results.s3.us-east-1.amazonaws.com/{s3_id}/{test_results_dir}/{filename}"
|
|
40
|
+
TEST_TAG_MAPPING = {
|
|
41
|
+
"failure": "failed",
|
|
42
|
+
"error": "aborted",
|
|
43
|
+
"skipped": "skipped"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def __init__(self, auth_token: str, base_url: str, api_version="v1") -> None:
|
|
47
|
+
self.results_path: Path | None = None
|
|
48
|
+
super().__init__(auth_token, base_url, api_version)
|
|
49
|
+
|
|
50
|
+
def _verify_required_files_exist(self, results_path: Path):
|
|
51
|
+
assert (results_path / self._junit_xml_filename).exists(), "Missing jUnit XML results file!"
|
|
52
|
+
assert (results_path / self._credentials_filename).exists(), "Missing test credentials file!"
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _verify_env(env: SirenadaEnv):
|
|
56
|
+
for key in SirenadaEnv.__annotations__.keys():
|
|
57
|
+
assert env.get(key), f"Missing required key {key} in the environment"
|
|
58
|
+
|
|
59
|
+
def _set_files_for_failed_test(self, test_case: RawSirenadaTestCase):
|
|
60
|
+
test_results_dir = f"sirenada-{test_case['sirenada_test_id']}"
|
|
61
|
+
results_dir: Path = self.results_path / test_results_dir
|
|
62
|
+
|
|
63
|
+
just_class_name = test_case.get("class_name").split(".")[-1]
|
|
64
|
+
requests_pattern = f"{just_class_name}-{test_case.get('test_name')}.requests.json"
|
|
65
|
+
screenshot_pattern = f"{just_class_name}-{escape(test_case.get('test_name'))}-*.png"
|
|
66
|
+
|
|
67
|
+
if not results_dir.exists():
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if not (s3_bucket_id_path := (results_dir / "s3_id")).exists():
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
with open(s3_bucket_id_path, "rt") as s3_id_file:
|
|
74
|
+
test_case["s3_folder_id"] = s3_id_file.read().strip()
|
|
75
|
+
|
|
76
|
+
test_case["requests_file"] = self._bucket_url_template.format(
|
|
77
|
+
s3_id=test_case.get("s3_folder_id"),
|
|
78
|
+
test_results_dir=test_results_dir,
|
|
79
|
+
filename=requests_pattern,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
screenshots = glob(pathname=screenshot_pattern, root_dir=results_dir)
|
|
83
|
+
if len(screenshots) > 0:
|
|
84
|
+
test_case["screenshot_file"] = self._bucket_url_template.format(
|
|
85
|
+
s3_id=test_case.get("s3_folder_id"),
|
|
86
|
+
test_results_dir=test_results_dir,
|
|
87
|
+
filename=screenshots[0],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _set_case_status(self, raw_case: RawSirenadaTestCase, junit_case: ElementTree.Element):
|
|
91
|
+
subelements = list(junit_case.iter())
|
|
92
|
+
if len(subelements) == 2:
|
|
93
|
+
status_node = subelements[1]
|
|
94
|
+
raw_case["status"] = self.TEST_TAG_MAPPING.get(status_node.tag, "failed")
|
|
95
|
+
raw_case["message"] = status_node.get("message", "#NO_MESSAGE")
|
|
96
|
+
raw_case["stack_trace"] = status_node.text
|
|
97
|
+
self._set_files_for_failed_test(raw_case)
|
|
98
|
+
else:
|
|
99
|
+
raw_case["status"] = "passed"
|
|
100
|
+
|
|
101
|
+
def _parse_junit_results(self, junit_xml: Path, credentials: TestCredentials, env: SirenadaEnv) -> list[RawSirenadaTestCase]:
|
|
102
|
+
etree = ElementTree.parse(source=junit_xml)
|
|
103
|
+
cases = list(etree.iter("testcase"))
|
|
104
|
+
assert len(cases) > 0, "No test cases found in test results XML."
|
|
105
|
+
|
|
106
|
+
parsed_cases: list[RawSirenadaTestCase] = []
|
|
107
|
+
for case in cases:
|
|
108
|
+
raw_case: RawSirenadaTestCase = {}
|
|
109
|
+
raw_case["class_name"] = case.get("classname", "#NO_CLASSNAME")
|
|
110
|
+
raw_case["file_name"] = case.get("file", "#NO_FILE")
|
|
111
|
+
raw_case["test_name"] = case.get("name", "#NO_TEST_NAME")
|
|
112
|
+
raw_case["browser_type"] = env.get("SIRENADA_BROWSER")
|
|
113
|
+
raw_case["cluster_type"] = env.get("SIRENADA_CLUSTER")
|
|
114
|
+
raw_case["duration"] = float(case.attrib.get("time", "0.0"))
|
|
115
|
+
raw_case["sirenada_test_id"] = credentials.get("SIRENADA_TEST_ID")
|
|
116
|
+
raw_case["sirenada_user"] = credentials.get("SIRENADA_USER_NAME")
|
|
117
|
+
raw_case["sirenada_password"] = credentials.get("SIRENADA_USER_PASS")
|
|
118
|
+
self._set_case_status(raw_case, case)
|
|
119
|
+
parsed_cases.append(raw_case)
|
|
120
|
+
|
|
121
|
+
return parsed_cases
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _read_credentials(credentials_path: Path) -> TestCredentials:
|
|
125
|
+
with open(credentials_path, "rt") as creds_file:
|
|
126
|
+
credentials: TestCredentials = json.load(creds_file)
|
|
127
|
+
|
|
128
|
+
assert credentials.get("SIRENADA_TEST_ID"), "Credentials are missing required field: SIRENADA_TEST_ID"
|
|
129
|
+
return credentials
|
|
130
|
+
|
|
131
|
+
def submit_sirenada_run(self, env: SirenadaEnv, test_results_path: Path):
|
|
132
|
+
self.results_path = test_results_path
|
|
133
|
+
try:
|
|
134
|
+
self._verify_env(env)
|
|
135
|
+
self._verify_required_files_exist(self.results_path)
|
|
136
|
+
except AssertionError as exc:
|
|
137
|
+
LOGGER.critical("%s", exc.args[0])
|
|
138
|
+
raise exc
|
|
139
|
+
|
|
140
|
+
credentials = self._read_credentials(self.results_path / self._credentials_filename)
|
|
141
|
+
results = self._parse_junit_results(junit_xml=self.results_path / self._junit_xml_filename, credentials=credentials, env=env)
|
|
142
|
+
request_body: RawSirenadaRequest = {}
|
|
143
|
+
|
|
144
|
+
request_body["run_id"] = env.get("SIRENADA_JOB_ID")
|
|
145
|
+
request_body["build_id"] = env.get("JOB_NAME")
|
|
146
|
+
request_body["build_job_url"] = env.get("BUILD_URL")
|
|
147
|
+
request_body["region"] = env.get("SIRENADA_REGION")
|
|
148
|
+
request_body["results"] = results
|
|
149
|
+
|
|
150
|
+
response = self.submit_run(run_type=self.test_type, run_body=request_body)
|
|
151
|
+
self.check_response(response)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "argus-alm"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.0"
|
|
4
4
|
description = "Argus"
|
|
5
5
|
authors = ["Alexey Kartashov <alexey.kartashov@scylladb.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -15,19 +15,17 @@ optional = true
|
|
|
15
15
|
optional = true
|
|
16
16
|
|
|
17
17
|
[tool.poetry.dependencies]
|
|
18
|
+
requests = "^2.26.0"
|
|
18
19
|
python = "^3.10"
|
|
19
|
-
pydantic = "^1.8.2"
|
|
20
|
-
PyYAML = "^5.4.1"
|
|
21
|
-
scylla-driver = "^3.24.8"
|
|
22
|
-
|
|
23
20
|
|
|
24
21
|
[tool.poetry.group.web-backend.dependencies]
|
|
22
|
+
PyYAML = "^5.4.1"
|
|
23
|
+
scylla-driver = "^3.24.8"
|
|
25
24
|
Flask = "2.0.2"
|
|
26
25
|
Flask-WTF = "1.0.0"
|
|
27
26
|
Flask-Login = "0.5.0"
|
|
28
27
|
humanize = "3.13.1"
|
|
29
28
|
python-magic = "0.4.24"
|
|
30
|
-
requests = "2.26.0"
|
|
31
29
|
uwsgi = "2.0.20"
|
|
32
30
|
python-jenkins = "^1.7.0"
|
|
33
31
|
python-slugify = "^6.1.1"
|
|
@@ -10,24 +10,24 @@ packages = \
|
|
|
10
10
|
'argus.backend.plugins',
|
|
11
11
|
'argus.backend.plugins.driver_matrix_tests',
|
|
12
12
|
'argus.backend.plugins.sct',
|
|
13
|
+
'argus.backend.plugins.sirenada',
|
|
13
14
|
'argus.backend.service',
|
|
14
15
|
'argus.backend.util',
|
|
15
16
|
'argus.client',
|
|
16
17
|
'argus.client.driver_matrix_tests',
|
|
17
18
|
'argus.client.sct',
|
|
19
|
+
'argus.client.sirenada',
|
|
18
20
|
'argus.db']
|
|
19
21
|
|
|
20
22
|
package_data = \
|
|
21
23
|
{'': ['*']}
|
|
22
24
|
|
|
23
25
|
install_requires = \
|
|
24
|
-
['
|
|
25
|
-
'pydantic>=1.8.2,<2.0.0',
|
|
26
|
-
'scylla-driver>=3.24.8,<4.0.0']
|
|
26
|
+
['requests>=2.26.0,<3.0.0']
|
|
27
27
|
|
|
28
28
|
setup_kwargs = {
|
|
29
29
|
'name': 'argus-alm',
|
|
30
|
-
'version': '0.
|
|
30
|
+
'version': '0.11.0',
|
|
31
31
|
'description': 'Argus',
|
|
32
32
|
'long_description': '# Argus\n\n## Description\n\nArgus 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.\n\n## Installation notes\n\n### Prerequisites\n\n- Python >=3.10.0 (system-wide or pyenv)\n\n- NodeJS >=16 (with npm)\n\n- Yarn (can be installed globally with `npm -g install yarn`)\n\n- nginx\n\n- poetry >=1.2.0b1\n\n### From source\n\n#### Production\n\nPerform the following steps:\n\nCreate a user that will be used by uwsgi:\n\n```bash\nuseradd -m -s /bin/bash argus\nsudo -iu argus\n```\n\n(Optional) Install pyenv and create a virtualenv for this user:\n\n```bash\npyenv install 3.10.0\npyenv virtualenv argus\npyenv activate argus\n```\n\nClone the project into a directory somewhere where user has full write permissions\n\n```bash\ngit clone https://github.com/scylladb/argus ~/app\ncd ~/app\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`\n\n```bash\nyarn webpack\n```\n\nCreate 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.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `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.\n\nCopy nginx configuration file from `docs/configs/argus.nginx.conf` to nginx virtual hosts directory:\n\nUbuntu:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/sites-available/argus\nsudo ln -s /etc/nginx/sites-enabled/argus /etc/nginx/sites-available/argus\n```\n\nRHEL/Centos/Alma/Fedora:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/conf.d/argus.conf\n```\n\nAdjust the webhost settings in that file as necessary, particularly `listen` and `server_name` directives.\n\nCopy systemd service file from `docs/config/argus.service` to `/etc/systemd/system` directory:\n\n```bash\nsudo cp docs/config/argus.service /etc/systemd/system\n```\n\nOpen 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.\n\nWARNING: `start_argus.sh` assumes pyenv is installed into `~/.pyenv`\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now argus.service\n```\n\n#### Development\n\nClone the project into a directory somewhere\n\n```bash\ngit clone https://github.com/scylladb/argus\ncd argus\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`. Add --watch to recompile files on change.\n\n```bash\nyarn webpack --watch\n```\n##### Configuration\nCreate 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.\n\nSee `Production` section for more details.\nTo configure Github authentication follow steps:\n1. Authorize OAuth App\n 1. go to your Account Settings (top right corner) -> Developer settings (left pane) -> OAuth Apps\n 2. Click Create New OAuth App button\n 3. Fill the fields (app name: `argus-dev`, homepage URL `http://localhost:5000`, Auth callback URL: `http://localhost:5000/profile/oauth/github`)\n 4. Confirm and get the tokens/ids required for config\n2. Create Jenkins token for your account\n 1. Go to `Configure` in top right corner\n 2. Click `Add new Token`\n 3. Get it and paste to config to `JENKINS_API_TOKEN` param\n##### Database Initialization\n\nYou can initialize a scylla cluster in any way you like, either using docker image with docker-compose or using cassandra cluster manager. You will need to create the keyspace manually before you can sync database models.\n\nCreate keyspace according to your configuration.\ne.g. (need to test if it works with RF=1 if not, make it 3)\n```\nCREATE KEYSPACE argus WITH replication = {\'class\': \'SimpleStrategy\', \'replication_factor\' : 1}\n```\n\nInitial sync can be done as follows:\n\n```py\nfrom argus.backend.db import ScyllaCluster\nfrom argus.db.testrun import TestRun\ndb = ScyllaCluster.get()\n\ndb.sync_models() # Syncronizes Object Mapper models\nTestRun.init_own_table() # Syncronizes TestRun table (separate from python-driver Object Mapper)\n\n```\n\nYou can also use `flask sync-models` afterwards during development when making small changes to models.\n\nIt is recommended to set up jenkins api key and run `flask scan-jenkins` afterwards to get basic release/group/test structure.\n\nThere are scripts in `./scripts` directory that can be used to download data from production, upload them into your dev db and fix their relations to other models in your instance of the application. Specifically, `download_runs_from_prod.py` requires additional config, `argus.local.prod.yaml` which is the config used to connect to the production cluster. The scripts are split to prevent mistakes and accidentally affecting production cluster.\n\n##### Configuration\n\nCreate 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.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* and JENKINS_* variables with their respective values.\n\nRun the application from CLI using:\n\n```bash\nFLASK_ENV="development" FLASK_APP="argus_backend:start_server" FLASK_DEBUG=1 CQLENG_ALLOW_SCHEMA_MANAGEMENT=1 flask run\n```\n\nOmit `FLASK_DEBUG` if running your own debugger (pdb, pycharm, vscode)\n',
|
|
33
33
|
'author': 'Alexey Kartashov',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus-alm-0.10.2 → argus_alm-0.11.0}/argus/backend/plugins/driver_matrix_tests/raw_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|