argus-alm 0.10.1b4__py3-none-any.whl → 0.11.0__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/controller/api.py +0 -2
- argus/backend/controller/testrun_api.py +12 -0
- argus/backend/plugins/core.py +18 -0
- argus/backend/plugins/sct/testrun.py +6 -2
- argus/backend/plugins/sirenada/model.py +113 -0
- argus/backend/plugins/sirenada/plugin.py +17 -0
- argus/backend/plugins/sirenada/types.py +35 -0
- argus/backend/service/argus_service.py +18 -4
- argus/backend/service/stats.py +26 -3
- argus/backend/service/testrun.py +50 -7
- argus/client/base.py +2 -1
- argus/client/driver_matrix_tests/client.py +6 -6
- argus/client/sirenada/client.py +151 -0
- {argus_alm-0.10.1b4.dist-info → argus_alm-0.11.0.dist-info}/METADATA +2 -4
- {argus_alm-0.10.1b4.dist-info → argus_alm-0.11.0.dist-info}/RECORD +17 -13
- {argus_alm-0.10.1b4.dist-info → argus_alm-0.11.0.dist-info}/LICENSE +0 -0
- {argus_alm-0.10.1b4.dist-info → argus_alm-0.11.0.dist-info}/WHEEL +0 -0
argus/backend/controller/api.py
CHANGED
|
@@ -332,8 +332,6 @@ def set_test_plugin(test_id: str):
|
|
|
332
332
|
payload = get_payload(request)
|
|
333
333
|
|
|
334
334
|
current_user: User = g.user
|
|
335
|
-
if not current_user.is_admin():
|
|
336
|
-
raise Exception("Cannot set plugin as a non-administrator")
|
|
337
335
|
test: ArgusTest = ArgusTest.get(id=UUID(test_id))
|
|
338
336
|
test.plugin_name = payload["plugin_name"]
|
|
339
337
|
test.save()
|
|
@@ -257,3 +257,15 @@ def test_run_delete_comment(test_id: str, run_id: str, comment_id: str):
|
|
|
257
257
|
"status": "ok",
|
|
258
258
|
"response": result
|
|
259
259
|
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@bp.route("/terminate_stuck_runs", methods=["POST"])
|
|
263
|
+
@api_login_required
|
|
264
|
+
def sct_terminate_stuck_runs():
|
|
265
|
+
result = TestRunService().terminate_stuck_runs()
|
|
266
|
+
return {
|
|
267
|
+
"status": "ok",
|
|
268
|
+
"response": {
|
|
269
|
+
"total": result
|
|
270
|
+
}
|
|
271
|
+
}
|
argus/backend/plugins/core.py
CHANGED
|
@@ -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()
|
|
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
from cassandra.cqlengine import columns
|
|
7
|
+
from cassandra.cqlengine.models import _DoesNotExist
|
|
7
8
|
from argus.backend.db import ScyllaCluster
|
|
8
9
|
from argus.backend.models.web import ArgusRelease
|
|
9
10
|
from argus.backend.plugins.core import PluginModelBase
|
|
@@ -44,7 +45,7 @@ class SCTTestRunSubmissionRequest():
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
class SCTTestRun(PluginModelBase):
|
|
47
|
-
__table_name__ = "
|
|
48
|
+
__table_name__ = "sct_test_run"
|
|
48
49
|
_plugin_name = "scylla-cluster-tests"
|
|
49
50
|
|
|
50
51
|
# Test Details
|
|
@@ -105,7 +106,10 @@ class SCTTestRun(PluginModelBase):
|
|
|
105
106
|
run = cls()
|
|
106
107
|
run.build_id = req.job_name
|
|
107
108
|
run.assign_categories()
|
|
108
|
-
|
|
109
|
+
try:
|
|
110
|
+
run.assignee = run.get_scheduled_assignee()
|
|
111
|
+
except _DoesNotExist:
|
|
112
|
+
run.assignee = None
|
|
109
113
|
run.start_time = datetime.utcnow()
|
|
110
114
|
run.id = UUID(req.run_id) # pylint: disable=invalid-name
|
|
111
115
|
run.scm_revision_id = req.commit_id
|
|
@@ -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
|
|
@@ -24,6 +24,7 @@ from argus.backend.models.web import (
|
|
|
24
24
|
User,
|
|
25
25
|
)
|
|
26
26
|
from argus.backend.events.event_processors import EVENT_PROCESSORS
|
|
27
|
+
from argus.backend.service.testrun import TestRunService
|
|
27
28
|
|
|
28
29
|
LOGGER = logging.getLogger(__name__)
|
|
29
30
|
|
|
@@ -418,6 +419,19 @@ class ArgusService:
|
|
|
418
419
|
groups = ArgusScheduleGroup.filter(schedule_id=schedule.id).all()
|
|
419
420
|
assignees = ArgusScheduleAssignee.filter(schedule_id=schedule.id).all()
|
|
420
421
|
|
|
422
|
+
full_schedule = dict(schedule)
|
|
423
|
+
full_schedule["tests"] = [test.test_id for test in tests]
|
|
424
|
+
full_schedule["groups"] = [group.group_id for group in groups]
|
|
425
|
+
full_schedule["assignees"] = [assignee.assignee for assignee in assignees]
|
|
426
|
+
|
|
427
|
+
schedule_user = User.get(id=assignees[0].assignee)
|
|
428
|
+
|
|
429
|
+
jobs_for_schedule = self.get_jobs_for_user(user=schedule_user, ignore_time=True, schedules=[full_schedule])
|
|
430
|
+
|
|
431
|
+
service = TestRunService()
|
|
432
|
+
for job in jobs_for_schedule:
|
|
433
|
+
service.change_run_assignee(test_id=job["test_id"], run_id=job["id"], new_assignee=None)
|
|
434
|
+
|
|
421
435
|
for entities in [tests, groups, assignees]:
|
|
422
436
|
for entity in entities:
|
|
423
437
|
entity.delete()
|
|
@@ -517,15 +531,15 @@ class ArgusService:
|
|
|
517
531
|
|
|
518
532
|
return response
|
|
519
533
|
|
|
520
|
-
def get_jobs_for_user(self, user: User):
|
|
534
|
+
def get_jobs_for_user(self, user: User, ignore_time: bool = False, schedules: list[dict] = None):
|
|
521
535
|
runs = [run for plugin in all_plugin_models() for run in plugin.get_jobs_assigned_to_user(user=user)]
|
|
522
|
-
schedules = self.get_schedules_for_user(user)
|
|
536
|
+
schedules = self.get_schedules_for_user(user) if not schedules else schedules
|
|
523
537
|
valid_runs = []
|
|
524
538
|
today = datetime.datetime.now()
|
|
525
539
|
month_ago = today - datetime.timedelta(days=30)
|
|
526
540
|
for run in runs:
|
|
527
541
|
run_date = run["start_time"]
|
|
528
|
-
if user.id == run["assignee"] and run_date >= month_ago:
|
|
542
|
+
if user.id == run["assignee"] and run_date >= month_ago and not ignore_time:
|
|
529
543
|
valid_runs.append(run)
|
|
530
544
|
continue
|
|
531
545
|
for schedule in schedules:
|
|
@@ -545,7 +559,7 @@ class ArgusService:
|
|
|
545
559
|
break
|
|
546
560
|
return valid_runs
|
|
547
561
|
|
|
548
|
-
def get_schedules_for_user(self, user: User):
|
|
562
|
+
def get_schedules_for_user(self, user: User) -> list[dict]:
|
|
549
563
|
all_assigned_schedules = ArgusScheduleAssignee.filter(assignee=user.id).all()
|
|
550
564
|
schedule_keys = [(schedule_assignee.release_id, schedule_assignee.schedule_id)
|
|
551
565
|
for schedule_assignee in all_assigned_schedules]
|
argus/backend/service/stats.py
CHANGED
|
@@ -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):
|
argus/backend/service/testrun.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from datetime import datetime
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
@@ -34,6 +34,10 @@ from argus.backend.util.enums import TestInvestigationStatus, TestStatus
|
|
|
34
34
|
LOGGER = logging.getLogger(__name__)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
class TestRunServiceException(Exception):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
37
41
|
class TestRunService:
|
|
38
42
|
ASSIGNEE_PLACEHOLDER = "none-none-none"
|
|
39
43
|
|
|
@@ -64,14 +68,14 @@ class TestRunService:
|
|
|
64
68
|
if not plugin:
|
|
65
69
|
return []
|
|
66
70
|
|
|
67
|
-
last_runs: list[
|
|
68
|
-
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]
|
|
69
73
|
for added_run in additional_runs:
|
|
70
74
|
if added_run not in last_runs_ids:
|
|
71
|
-
last_runs.
|
|
75
|
+
last_runs.extend(plugin.model.get_run_meta_by_run_id(run_id=added_run))
|
|
72
76
|
|
|
73
77
|
for row in last_runs:
|
|
74
|
-
|
|
78
|
+
row["build_number"] = get_build_number(build_job_url=row["build_job_url"])
|
|
75
79
|
|
|
76
80
|
return last_runs
|
|
77
81
|
|
|
@@ -91,7 +95,10 @@ class TestRunService:
|
|
|
91
95
|
return response
|
|
92
96
|
|
|
93
97
|
def change_run_status(self, test_id: UUID, run_id: UUID, new_status: TestStatus):
|
|
94
|
-
|
|
98
|
+
try:
|
|
99
|
+
test = ArgusTest.get(id=test_id)
|
|
100
|
+
except ArgusTest.DoesNotExist as exc:
|
|
101
|
+
raise TestRunServiceException("Test entity does not exist for provided test_id", test_id) from exc
|
|
95
102
|
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
96
103
|
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
97
104
|
old_status = run.status
|
|
@@ -384,7 +391,7 @@ class TestRunService:
|
|
|
384
391
|
response = []
|
|
385
392
|
for issue in all_issues:
|
|
386
393
|
runs = runs_by_issue.get(issue, [])
|
|
387
|
-
runs.append(issue.run_id)
|
|
394
|
+
runs.append({"test_id": issue.test_id, "run_id": issue.run_id})
|
|
388
395
|
runs_by_issue[issue] = runs
|
|
389
396
|
|
|
390
397
|
for issue, runs in runs_by_issue.items():
|
|
@@ -419,3 +426,39 @@ class TestRunService:
|
|
|
419
426
|
return {
|
|
420
427
|
"deleted": issue_id
|
|
421
428
|
}
|
|
429
|
+
|
|
430
|
+
def terminate_stuck_runs(self):
|
|
431
|
+
sct = AVAILABLE_PLUGINS.get("scylla-cluster-tests").model
|
|
432
|
+
now = datetime.utcnow()
|
|
433
|
+
stuck_period = now - timedelta(minutes=45)
|
|
434
|
+
stuck_runs_running = sct.filter(heartbeat__lt=int(
|
|
435
|
+
stuck_period.timestamp()), status=TestStatus.RUNNING.value).allow_filtering().all()
|
|
436
|
+
stuck_runs_created = sct.filter(heartbeat__lt=int(
|
|
437
|
+
stuck_period.timestamp()), status=TestStatus.CREATED.value).allow_filtering().all()
|
|
438
|
+
|
|
439
|
+
all_stuck_runs = [*stuck_runs_running, *stuck_runs_created]
|
|
440
|
+
LOGGER.info("Found %s stuck runs", len(all_stuck_runs))
|
|
441
|
+
|
|
442
|
+
for run in all_stuck_runs:
|
|
443
|
+
LOGGER.info("Will set %s as ABORTED", run.id)
|
|
444
|
+
old_status = run.status
|
|
445
|
+
run.status = TestStatus.ABORTED.value
|
|
446
|
+
run.save()
|
|
447
|
+
|
|
448
|
+
self.create_run_event(
|
|
449
|
+
kind=ArgusEventTypes.TestRunStatusChanged,
|
|
450
|
+
body={
|
|
451
|
+
"message": "Run was automatically terminated due to not responding for more than 45 minutes "
|
|
452
|
+
"(Status changed from {old_status} to {new_status}) by {username}",
|
|
453
|
+
"old_status": old_status,
|
|
454
|
+
"new_status": run.status,
|
|
455
|
+
"username": g.user.username
|
|
456
|
+
},
|
|
457
|
+
user_id=g.user.id,
|
|
458
|
+
run_id=run.id,
|
|
459
|
+
release_id=run.release_id,
|
|
460
|
+
group_id=run.group_id,
|
|
461
|
+
test_id=run.test_id
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return len(all_stuck_runs)
|
argus/client/base.py
CHANGED
|
@@ -59,9 +59,10 @@ class ArgusClient:
|
|
|
59
59
|
response_data: JSON = response.json()
|
|
60
60
|
LOGGER.debug("API Response: %s", response_data)
|
|
61
61
|
if response_data.get("status") != "ok":
|
|
62
|
+
exc_args = response_data["response"]["arguments"]
|
|
62
63
|
raise ArgusClientError(
|
|
63
64
|
f"API Error encountered using endpoint: {response.request.method} {response.request.path_url}",
|
|
64
|
-
response_data
|
|
65
|
+
exc_args[0] if len(exc_args) > 0 else response_data.get("response", {}).get("exception", "#NoMessage"),
|
|
65
66
|
)
|
|
66
67
|
|
|
67
68
|
def get_url_for_endpoint(self, endpoint: str, location_params: dict[str, str] | None) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from functools import reduce
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from typing import TypedDict
|
|
3
|
+
from typing import TypedDict, Literal
|
|
4
4
|
import re
|
|
5
5
|
import logging
|
|
6
6
|
from uuid import UUID
|
|
@@ -124,7 +124,7 @@ class ArgusDriverMatrixClient(ArgusClient):
|
|
|
124
124
|
|
|
125
125
|
return total - errors - skipped - failures
|
|
126
126
|
|
|
127
|
-
def parse_result_xml(self, xml_path: Path, test_type:
|
|
127
|
+
def parse_result_xml(self, xml_path: Path, test_type: Literal['java', 'cpp', 'python']) -> RawMatrixTestResult:
|
|
128
128
|
with xml_path.open(mode="rt", encoding="utf-8") as xml_file:
|
|
129
129
|
xml = ElementTree.parse(source=xml_file)
|
|
130
130
|
LOGGER.info("%s", pformat(xml))
|
|
@@ -171,18 +171,18 @@ class ArgusDriverMatrixClient(ArgusClient):
|
|
|
171
171
|
"suites": all_suites
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
def get_results(self, result_path: str, test_type:
|
|
174
|
+
def get_results(self, result_path: str, test_type: Literal['java', 'cpp', 'python']) -> list[RawMatrixTestResult]:
|
|
175
175
|
xmls = glob(f"{result_path}/**/*.xml", recursive=True)
|
|
176
176
|
LOGGER.info("Will use following XMLs: %s", pformat(xmls))
|
|
177
177
|
results = []
|
|
178
178
|
for xml in xmls:
|
|
179
|
-
result = self.parse_result_xml(Path(xml), test_type
|
|
179
|
+
result = self.parse_result_xml(Path(xml), test_type)
|
|
180
180
|
results.append(result)
|
|
181
181
|
return results
|
|
182
182
|
|
|
183
|
-
def submit(self, build_id: str, build_url: str, env_path: str, result_path: str, test_type:
|
|
183
|
+
def submit(self, build_id: str, build_url: str, env_path: str, result_path: str, test_type: Literal['java', 'cpp', 'python']):
|
|
184
184
|
env = self.parse_build_environment(env_path)
|
|
185
|
-
results = self.get_results(result_path, test_type
|
|
185
|
+
results = self.get_results(result_path, test_type)
|
|
186
186
|
|
|
187
187
|
self.submit_driver_matrix_run(job_name=build_id, job_url=build_url, test_environment=env, results=results)
|
|
188
188
|
failures = reduce(lambda total, coll: (coll["failures"] + coll["errors"]) + total, results, 0)
|
|
@@ -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
|
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
|
|
@@ -11,9 +11,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist: pydantic (>=1.8.2,<2.0.0)
|
|
16
|
-
Requires-Dist: scylla-driver (>=3.24.8,<4.0.0)
|
|
14
|
+
Requires-Dist: requests (>=2.26.0,<3.0.0)
|
|
17
15
|
Project-URL: Repository, https://github.com/scylladb/argus
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
|
|
@@ -5,7 +5,7 @@ argus/backend/cli.py,sha256=EZFI70CIaHNt93ARvEkNanbC0_nB7loIPq9giEQ3P2s,1342
|
|
|
5
5
|
argus/backend/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
argus/backend/controller/admin.py,sha256=2z29RX7ZQO_VTklSKH9RrEj-Ag2SsvyOaIzWDKr0ahQ,575
|
|
7
7
|
argus/backend/controller/admin_api.py,sha256=6-BSQpeaMIq1WNAJX_rIe5x3-aslDDfolzhKHOu3yvM,5545
|
|
8
|
-
argus/backend/controller/api.py,sha256=
|
|
8
|
+
argus/backend/controller/api.py,sha256=Gb2ziDYSWSFYNzUEtffQdoMaF1MF0gQ7zNxPV6I6Eqg,13111
|
|
9
9
|
argus/backend/controller/auth.py,sha256=7qrMUkBc89hTwTdzv-_1l4zkKsh8YNhWt7uXIGalAmI,2029
|
|
10
10
|
argus/backend/controller/client_api.py,sha256=Oecv0b61O66KYcLBbwueunONC5S6A6vks3r60PKoD6w,2612
|
|
11
11
|
argus/backend/controller/main.py,sha256=3lYbR3gz-X-Ke6X6--_a-SxneaUwpMb0DqOR-iGikiM,8304
|
|
@@ -13,14 +13,14 @@ argus/backend/controller/notification_api.py,sha256=wz7V4nE6Mxclpq78P8gNnCyeQ7xA
|
|
|
13
13
|
argus/backend/controller/notifications.py,sha256=zMSJln72BGU6Q_nQvJesMnuvJ57Ucbov4M2ZI-37Bxo,290
|
|
14
14
|
argus/backend/controller/team.py,sha256=ILdTACMK4VB9BNNad3PXf-_LLEAoGlHf0SkqDvDJPII,3095
|
|
15
15
|
argus/backend/controller/team_ui.py,sha256=B7N1_Kzl6Rac8BV3FbKj55pGAS_dht47rYhAi94PC8A,589
|
|
16
|
-
argus/backend/controller/testrun_api.py,sha256=
|
|
16
|
+
argus/backend/controller/testrun_api.py,sha256=DuWgS2VJPnlzZrpyW00kdnJD-FhLj59c7Pitu8cG5k8,7523
|
|
17
17
|
argus/backend/db.py,sha256=bBiraYD05Qex28yZHjSP1bRlcMsc6oTYGt792zXmaHo,4101
|
|
18
18
|
argus/backend/error_handlers.py,sha256=IEjz7Vzfldv1PTOeHrpRWmRsgBrHtAW0PXHUJZDovAE,480
|
|
19
19
|
argus/backend/events/event_processors.py,sha256=-GKSE4xNFXtjwR9QuRpWbfA_ixarMrGaZYppIW400Hw,1188
|
|
20
20
|
argus/backend/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
argus/backend/models/web.py,sha256=imXIbp9VSEywzB4hh5uQXf_PlL4wN11XLyalnISbHwU,12069
|
|
22
22
|
argus/backend/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
argus/backend/plugins/core.py,sha256=
|
|
23
|
+
argus/backend/plugins/core.py,sha256=VnlCFdWDBYMWi7R-e5Cb-cG3JWFNOlA94Zq98VDsaQQ,6562
|
|
24
24
|
argus/backend/plugins/driver_matrix_tests/controller.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
argus/backend/plugins/driver_matrix_tests/model.py,sha256=DObVIfN8NXLrqdhmx-kPkS0rLvlL_B0eEwlUPXqN8Cs,4748
|
|
26
26
|
argus/backend/plugins/driver_matrix_tests/plugin.py,sha256=NZN6lpHFpNuBkv2D_gGHh03402z0qlOeCWjgZXRs2c4,637
|
|
@@ -31,17 +31,20 @@ argus/backend/plugins/sct/controller.py,sha256=psm08pLQQCTTKP8xGdCe6YQS_AQoOBbR1
|
|
|
31
31
|
argus/backend/plugins/sct/plugin.py,sha256=-ezQ7brAdgQ_YJpZiCaVBNuzP91FtBiKWf_IXy70xgg,902
|
|
32
32
|
argus/backend/plugins/sct/resource_setup.py,sha256=hwfAOu-oKOH42tjtzJhiqwq_MtUE9_HevoFyql8JKqY,10120
|
|
33
33
|
argus/backend/plugins/sct/service.py,sha256=YIyPtiHY0VMW9n6icVWeM7z9OF3WcLNQ7LJZCpF8akE,8723
|
|
34
|
-
argus/backend/plugins/sct/testrun.py,sha256=
|
|
34
|
+
argus/backend/plugins/sct/testrun.py,sha256=Amj2-Wg2go2hd8EJc1upuGnff_RhpD4c3KXIoORJQGg,6556
|
|
35
35
|
argus/backend/plugins/sct/udt.py,sha256=j1Q7sidzYXxP8ELkkaT-YeJqhe7qixNU9tbNuzLQ5_8,2420
|
|
36
|
+
argus/backend/plugins/sirenada/model.py,sha256=hZZvpUBp8Ch-hWVGjT_ua3xxhQicEIUK6NLGQGhAkts,4639
|
|
37
|
+
argus/backend/plugins/sirenada/plugin.py,sha256=AlQAakwy3u-OqAqqK3RonUR5oDm-JoiwBUDUF3YEVP4,447
|
|
38
|
+
argus/backend/plugins/sirenada/types.py,sha256=Gm3XMK9YJoozVaeM9XE7n8iRxA6PKBrS23Mo2vJfdLs,697
|
|
36
39
|
argus/backend/service/admin.py,sha256=_VnWl3CkZBOAie_pPbd9sbXZUpBf2SApyNoFZLfB_QI,637
|
|
37
|
-
argus/backend/service/argus_service.py,sha256=
|
|
40
|
+
argus/backend/service/argus_service.py,sha256=hF_-d6dNyHBx_KO-RjH-a7MK8T3cjLI2cR5XoHBObKc,24865
|
|
38
41
|
argus/backend/service/build_system_monitor.py,sha256=ODxU2LxqJPOqU0r0sDSkxk73ZXjlaYngsJeXg4m-O5c,6586
|
|
39
42
|
argus/backend/service/client_service.py,sha256=wvR0PY5nCwKmIXuxrR_kNtY4WR8SLvRk6SOnxVU1myI,2109
|
|
40
43
|
argus/backend/service/notification_manager.py,sha256=h00Ej_-hH9H7pq0wah_1TH8dnpPyPNsgVJNO1rwJi7o,7011
|
|
41
44
|
argus/backend/service/release_manager.py,sha256=v2yg45oWBpK_uwqkHtJHPTPbe21_cJu3skmzeaChVhw,6259
|
|
42
|
-
argus/backend/service/stats.py,sha256=
|
|
45
|
+
argus/backend/service/stats.py,sha256=R8krmGi2RsOBSV3ZHDS9crkXSogiPqSnDE2LRYy3APs,12274
|
|
43
46
|
argus/backend/service/team_manager_service.py,sha256=zY5dvy3ffvQbJuXBvlWKE5dS5LQ3ss6tkFE-cwFZsdw,3010
|
|
44
|
-
argus/backend/service/testrun.py,sha256=
|
|
47
|
+
argus/backend/service/testrun.py,sha256=wCHowQoD_PkTdpyXLQ1EXxkU_pfvgpaDde5xOUD5Fn8,17883
|
|
45
48
|
argus/backend/service/user.py,sha256=uvhOOvMgrYJHV1I6RH3P-h1d--TlO55uvum8eOpHyxQ,8858
|
|
46
49
|
argus/backend/template_filters.py,sha256=hD8eDyBYp-X6JVoyQhM-TWYv3MuyorAv0Emz728iGcU,523
|
|
47
50
|
argus/backend/util/common.py,sha256=fmE9cKnJ1CX_Cz4Mw1d_M-T-jwPh8DkKr_fLAx8LY9M,1458
|
|
@@ -52,10 +55,11 @@ argus/backend/util/logsetup.py,sha256=XWyFLC1_J5G8NcR7oC9l-Pf02ybAvEZR95y5LoY4W4
|
|
|
52
55
|
argus/backend/util/module_loaders.py,sha256=AcIlX-VRmUQ2THFKT8DLefLSE62Eub2hCxIosg3WgE0,698
|
|
53
56
|
argus/backend/util/send_email.py,sha256=Bb2Hta7WlBCvDKga0_WPFWgxWJEfKpectOGypgf9xzo,3217
|
|
54
57
|
argus/client/__init__.py,sha256=bO9_j5_jK5kvTHR46KEZ0Y-p0li7CBW8QSd-K5Ez4vA,42
|
|
55
|
-
argus/client/base.py,sha256=
|
|
56
|
-
argus/client/driver_matrix_tests/client.py,sha256=
|
|
58
|
+
argus/client/base.py,sha256=q0awF6GX4Ufi-3IjfBTYvh4dZAfBQCqimEKcSxlvMmg,5972
|
|
59
|
+
argus/client/driver_matrix_tests/client.py,sha256=pnyE2YSSyBJ9XZjG-z_hEaKQJV7oP6hFaxDqUjxVY5o,7585
|
|
57
60
|
argus/client/sct/client.py,sha256=mc44QXDwxgXAY51yO0ui9w9V_tNgt4-M_yq__8fvnXQ,8217
|
|
58
61
|
argus/client/sct/types.py,sha256=VLgVe7qPmJtCLqtPnuX8N8kMKZq-iY3SKz68nvU6nJ4,371
|
|
62
|
+
argus/client/sirenada/client.py,sha256=jpvvHsRf5VSQKSmnamvK054ZlVXHM6w8UXDiUhFEF0A,6222
|
|
59
63
|
argus/db/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
64
|
argus/db/argus_json.py,sha256=zwu7yug0_oUmt5TbcvIQ58uC1PxloEOj5udqoPJn6jQ,391
|
|
61
65
|
argus/db/cloud_types.py,sha256=KXk6bzFrcIH_0b9L7m9CYiU1YkEeL7ZDpHqH-XKONsY,3702
|
|
@@ -64,7 +68,7 @@ argus/db/db_types.py,sha256=iLbmrUaDzrBw0kDCnvW0FSZ9-kNc3uQY-fsbIPymV4E,3612
|
|
|
64
68
|
argus/db/interface.py,sha256=HroyA1Yijz5cXLdYbxorHCEu0GH9VeMMqB36IHTlcew,17146
|
|
65
69
|
argus/db/testrun.py,sha256=0YG7FIH5FLQeNlYULxC6rhhyru2rziSMe3qKtYzTBnc,26014
|
|
66
70
|
argus/db/utils.py,sha256=YAWsuLjUScSgKgdaL5aF4Sgr13gqH29Mb5cLctX4V_w,337
|
|
67
|
-
argus_alm-0.
|
|
68
|
-
argus_alm-0.
|
|
69
|
-
argus_alm-0.
|
|
70
|
-
argus_alm-0.
|
|
71
|
+
argus_alm-0.11.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
72
|
+
argus_alm-0.11.0.dist-info/METADATA,sha256=Hima5dK6luGSWBfNFa4S2PTLPhsMspRh_w24VlHR1L4,6824
|
|
73
|
+
argus_alm-0.11.0.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
|
74
|
+
argus_alm-0.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|