argus-alm 0.12.10__py3-none-any.whl → 0.13.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/client/base.py +1 -1
- argus/client/driver_matrix_tests/cli.py +2 -2
- argus/client/driver_matrix_tests/client.py +1 -1
- argus/client/generic/cli.py +2 -2
- argus/client/sct/client.py +3 -3
- argus/client/sirenada/client.py +1 -1
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/METADATA +2 -4
- argus_alm-0.13.0.dist-info/RECORD +20 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/cli.py +0 -41
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +0 -20
- argus/backend/controller/admin_api.py +0 -354
- argus/backend/controller/api.py +0 -529
- argus/backend/controller/auth.py +0 -67
- argus/backend/controller/client_api.py +0 -108
- argus/backend/controller/main.py +0 -274
- argus/backend/controller/notification_api.py +0 -72
- argus/backend/controller/notifications.py +0 -13
- argus/backend/controller/team.py +0 -126
- argus/backend/controller/team_ui.py +0 -18
- argus/backend/controller/testrun_api.py +0 -482
- argus/backend/controller/view_api.py +0 -162
- argus/backend/db.py +0 -100
- argus/backend/error_handlers.py +0 -21
- argus/backend/events/event_processors.py +0 -34
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/result.py +0 -138
- argus/backend/models/web.py +0 -389
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +0 -225
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -63
- argus/backend/plugins/driver_matrix_tests/model.py +0 -421
- argus/backend/plugins/driver_matrix_tests/plugin.py +0 -22
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
- argus/backend/plugins/driver_matrix_tests/service.py +0 -60
- argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
- argus/backend/plugins/generic/model.py +0 -79
- argus/backend/plugins/generic/plugin.py +0 -16
- argus/backend/plugins/generic/types.py +0 -13
- argus/backend/plugins/loader.py +0 -40
- argus/backend/plugins/sct/controller.py +0 -185
- argus/backend/plugins/sct/plugin.py +0 -38
- argus/backend/plugins/sct/resource_setup.py +0 -178
- argus/backend/plugins/sct/service.py +0 -491
- argus/backend/plugins/sct/testrun.py +0 -272
- argus/backend/plugins/sct/udt.py +0 -101
- argus/backend/plugins/sirenada/model.py +0 -113
- argus/backend/plugins/sirenada/plugin.py +0 -17
- argus/backend/service/admin.py +0 -27
- argus/backend/service/argus_service.py +0 -688
- argus/backend/service/build_system_monitor.py +0 -188
- argus/backend/service/client_service.py +0 -122
- argus/backend/service/event_service.py +0 -18
- argus/backend/service/jenkins_service.py +0 -240
- argus/backend/service/notification_manager.py +0 -150
- argus/backend/service/release_manager.py +0 -230
- argus/backend/service/results_service.py +0 -317
- argus/backend/service/stats.py +0 -540
- argus/backend/service/team_manager_service.py +0 -83
- argus/backend/service/testrun.py +0 -559
- argus/backend/service/user.py +0 -307
- argus/backend/service/views.py +0 -258
- argus/backend/template_filters.py +0 -27
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/argus_web.test.yaml +0 -39
- argus/backend/tests/conftest.py +0 -44
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +0 -70
- argus/backend/util/common.py +0 -65
- argus/backend/util/config.py +0 -38
- argus/backend/util/encoders.py +0 -41
- argus/backend/util/logsetup.py +0 -81
- argus/backend/util/module_loaders.py +0 -30
- argus/backend/util/send_email.py +0 -91
- argus/client/generic_result_old.py +0 -143
- argus/db/.gitkeep +0 -0
- argus/db/argus_json.py +0 -14
- argus/db/cloud_types.py +0 -125
- argus/db/config.py +0 -135
- argus/db/db_types.py +0 -139
- argus/db/interface.py +0 -370
- argus/db/testrun.py +0 -740
- argus/db/utils.py +0 -15
- argus_alm-0.12.10.dist-info/RECORD +0 -96
- /argus/{backend → common}/__init__.py +0 -0
- /argus/{backend/util → common}/enums.py +0 -0
- /argus/{backend/plugins/sct/types.py → common/sct_types.py} +0 -0
- /argus/{backend/plugins/sirenada/types.py → common/sirenada_types.py} +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/LICENSE +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/WHEEL +0 -0
- {argus_alm-0.12.10.dist-info → argus_alm-0.13.0.dist-info}/entry_points.txt +0 -0
argus/backend/service/testrun.py
DELETED
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
|
-
from datetime import datetime, timedelta
|
|
3
|
-
from functools import reduce
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
import re
|
|
7
|
-
import time
|
|
8
|
-
from typing import Any
|
|
9
|
-
from uuid import UUID
|
|
10
|
-
|
|
11
|
-
import requests
|
|
12
|
-
from flask import g
|
|
13
|
-
from cassandra.query import BatchStatement, ConsistencyLevel
|
|
14
|
-
from cassandra.cqlengine.query import BatchQuery
|
|
15
|
-
from argus.backend.db import ScyllaCluster
|
|
16
|
-
from argus.backend.models.result import ArgusGenericResultMetadata
|
|
17
|
-
|
|
18
|
-
from argus.backend.models.web import (
|
|
19
|
-
ArgusEvent,
|
|
20
|
-
ArgusEventTypes,
|
|
21
|
-
ArgusGithubIssue,
|
|
22
|
-
ArgusGroup,
|
|
23
|
-
ArgusNotificationSourceTypes,
|
|
24
|
-
ArgusNotificationTypes,
|
|
25
|
-
ArgusRelease,
|
|
26
|
-
ArgusTest,
|
|
27
|
-
ArgusTestRunComment,
|
|
28
|
-
ArgusUserView,
|
|
29
|
-
User,
|
|
30
|
-
UserOauthToken,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
|
|
34
|
-
|
|
35
|
-
from argus.backend.plugins.loader import AVAILABLE_PLUGINS
|
|
36
|
-
from argus.backend.events.event_processors import EVENT_PROCESSORS
|
|
37
|
-
from argus.backend.service.event_service import EventService
|
|
38
|
-
from argus.backend.service.notification_manager import NotificationManagerService
|
|
39
|
-
from argus.backend.service.stats import ComparableTestStatus
|
|
40
|
-
from argus.backend.util.common import chunk, get_build_number, strip_html_tags
|
|
41
|
-
from argus.backend.util.enums import TestInvestigationStatus, TestStatus
|
|
42
|
-
|
|
43
|
-
LOGGER = logging.getLogger(__name__)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class TestRunServiceException(Exception):
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class TestRunService:
|
|
51
|
-
ASSIGNEE_PLACEHOLDER = "none-none-none"
|
|
52
|
-
|
|
53
|
-
RE_MENTION = r"@[A-Za-z\d](?:[A-Za-z\d]|-(?=[A-Za-z\d])){0,38}"
|
|
54
|
-
|
|
55
|
-
plugins = AVAILABLE_PLUGINS
|
|
56
|
-
github_headers = {
|
|
57
|
-
"Accept": "application/vnd.github.v3+json",
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
def __init__(self) -> None:
|
|
61
|
-
self.notification_manager = NotificationManagerService()
|
|
62
|
-
|
|
63
|
-
def get_plugin(self, plugin_name: str) -> PluginInfoBase | None:
|
|
64
|
-
return self.plugins.get(plugin_name)
|
|
65
|
-
|
|
66
|
-
def get_run(self, run_type: str, run_id: UUID) -> PluginModelBase:
|
|
67
|
-
plugin = self.plugins.get(run_type)
|
|
68
|
-
if plugin:
|
|
69
|
-
try:
|
|
70
|
-
return plugin.model.get(id=run_id)
|
|
71
|
-
except plugin.model.DoesNotExist:
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
def get_runs_by_test_id(self, test_id: UUID, additional_runs: list[UUID], limit: int = 10):
|
|
75
|
-
test: ArgusTest = ArgusTest.get(id=test_id)
|
|
76
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
77
|
-
if not plugin:
|
|
78
|
-
return []
|
|
79
|
-
|
|
80
|
-
last_runs: list[dict] = plugin.model.get_run_meta_by_build_id(build_id=test.build_system_id, limit=limit)
|
|
81
|
-
last_runs_ids = [run["id"] for run in last_runs]
|
|
82
|
-
for added_run in additional_runs:
|
|
83
|
-
if added_run not in last_runs_ids:
|
|
84
|
-
last_runs.extend(plugin.model.get_run_meta_by_run_id(run_id=added_run))
|
|
85
|
-
|
|
86
|
-
for row in last_runs:
|
|
87
|
-
row["build_number"] = get_build_number(build_job_url=row["build_job_url"])
|
|
88
|
-
|
|
89
|
-
last_runs = sorted(last_runs, reverse=True, key=lambda run: (run["build_number"], ComparableTestStatus(TestStatus(run["status"]))))
|
|
90
|
-
|
|
91
|
-
return last_runs
|
|
92
|
-
|
|
93
|
-
def get_runs_by_id(self, test_id: UUID, runs: list[UUID]): # FIXME: Not needed, use get_run and individual polling
|
|
94
|
-
# This is a batch request.
|
|
95
|
-
test = ArgusTest.get(id=test_id)
|
|
96
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
97
|
-
polled_runs: list[PluginModelBase] = []
|
|
98
|
-
for run_id in runs:
|
|
99
|
-
try:
|
|
100
|
-
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
101
|
-
polled_runs.append(run)
|
|
102
|
-
except plugin.model.DoesNotExist:
|
|
103
|
-
pass
|
|
104
|
-
|
|
105
|
-
response = {str(run.id): run for run in polled_runs}
|
|
106
|
-
return response
|
|
107
|
-
|
|
108
|
-
def change_run_status(self, test_id: UUID, run_id: UUID, new_status: TestStatus):
|
|
109
|
-
try:
|
|
110
|
-
test = ArgusTest.get(id=test_id)
|
|
111
|
-
except ArgusTest.DoesNotExist as exc:
|
|
112
|
-
raise TestRunServiceException("Test entity does not exist for provided test_id", test_id) from exc
|
|
113
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
114
|
-
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
115
|
-
old_status = run.status
|
|
116
|
-
run.status = new_status.value
|
|
117
|
-
run.save()
|
|
118
|
-
|
|
119
|
-
EventService.create_run_event(
|
|
120
|
-
kind=ArgusEventTypes.TestRunStatusChanged,
|
|
121
|
-
body={
|
|
122
|
-
"message": "Status was changed from {old_status} to {new_status} by {username}",
|
|
123
|
-
"old_status": old_status,
|
|
124
|
-
"new_status": new_status.value,
|
|
125
|
-
"username": g.user.username
|
|
126
|
-
},
|
|
127
|
-
user_id=g.user.id,
|
|
128
|
-
run_id=run.id,
|
|
129
|
-
release_id=test.release_id,
|
|
130
|
-
group_id=test.group_id,
|
|
131
|
-
test_id=test.id
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
"test_run_id": run.id,
|
|
136
|
-
"status": new_status
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
def change_run_investigation_status(self, test_id: UUID, run_id: UUID, new_status: TestInvestigationStatus):
|
|
140
|
-
test = ArgusTest.get(id=test_id)
|
|
141
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
142
|
-
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
143
|
-
old_status = run.investigation_status
|
|
144
|
-
run.investigation_status = new_status.value
|
|
145
|
-
run.save()
|
|
146
|
-
|
|
147
|
-
EventService.create_run_event(
|
|
148
|
-
kind=ArgusEventTypes.TestRunStatusChanged,
|
|
149
|
-
body={
|
|
150
|
-
"message": "Investigation status was changed from {old_status} to {new_status} by {username}",
|
|
151
|
-
"old_status": old_status,
|
|
152
|
-
"new_status": new_status.value,
|
|
153
|
-
"username": g.user.username
|
|
154
|
-
},
|
|
155
|
-
user_id=g.user.id,
|
|
156
|
-
run_id=run.id,
|
|
157
|
-
release_id=test.release_id,
|
|
158
|
-
group_id=test.group_id,
|
|
159
|
-
test_id=test.id
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
"test_run_id": run.id,
|
|
164
|
-
"investigation_status": new_status
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
def change_run_assignee(self, test_id: UUID, run_id: UUID, new_assignee: UUID | None):
|
|
168
|
-
test = ArgusTest.get(id=test_id)
|
|
169
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
170
|
-
if not plugin:
|
|
171
|
-
return {
|
|
172
|
-
"test_run_id": run.id,
|
|
173
|
-
"assignee": None
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
177
|
-
old_assignee = run.assignee
|
|
178
|
-
run.assignee = new_assignee
|
|
179
|
-
run.save()
|
|
180
|
-
|
|
181
|
-
if new_assignee:
|
|
182
|
-
new_assignee_user = User.get(id=new_assignee)
|
|
183
|
-
else:
|
|
184
|
-
new_assignee_user = None
|
|
185
|
-
if old_assignee:
|
|
186
|
-
try:
|
|
187
|
-
old_assignee_user = User.get(id=old_assignee)
|
|
188
|
-
except User.DoesNotExist:
|
|
189
|
-
LOGGER.warning("Non existent assignee was present on the run %s for test %s: %s",
|
|
190
|
-
run_id, test_id, old_assignee)
|
|
191
|
-
old_assignee = None
|
|
192
|
-
EventService.create_run_event(
|
|
193
|
-
kind=ArgusEventTypes.AssigneeChanged,
|
|
194
|
-
body={
|
|
195
|
-
"message": "Assignee was changed from \"{old_user}\" to \"{new_user}\" by {username}",
|
|
196
|
-
"old_user": old_assignee_user.username if old_assignee else "None",
|
|
197
|
-
"new_user": new_assignee_user.username if new_assignee else "None",
|
|
198
|
-
"username": g.user.username
|
|
199
|
-
},
|
|
200
|
-
user_id=g.user.id,
|
|
201
|
-
run_id=run.id,
|
|
202
|
-
release_id=test.release_id,
|
|
203
|
-
group_id=test.group_id,
|
|
204
|
-
test_id=test.id
|
|
205
|
-
)
|
|
206
|
-
if new_assignee_user.id != g.user.id:
|
|
207
|
-
self.notification_manager.send_notification(
|
|
208
|
-
receiver=new_assignee_user.id,
|
|
209
|
-
sender=g.user.id,
|
|
210
|
-
notification_type=ArgusNotificationTypes.AssigneeChange,
|
|
211
|
-
source_type=ArgusNotificationSourceTypes.TestRun,
|
|
212
|
-
source_id=run.id,
|
|
213
|
-
source_message=str(run.test_id),
|
|
214
|
-
content_params={
|
|
215
|
-
"username": g.user.username,
|
|
216
|
-
"run_id": run.id,
|
|
217
|
-
"test_id": test.id,
|
|
218
|
-
"build_id": run.build_id,
|
|
219
|
-
"build_number": get_build_number(run.build_job_url),
|
|
220
|
-
}
|
|
221
|
-
)
|
|
222
|
-
return {
|
|
223
|
-
"test_run_id": run.id,
|
|
224
|
-
"assignee": str(new_assignee_user.id) if new_assignee_user else None
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
def get_run_comment(self, comment_id: UUID):
|
|
228
|
-
try:
|
|
229
|
-
return ArgusTestRunComment.get(id=comment_id)
|
|
230
|
-
except ArgusTestRunComment.DoesNotExist:
|
|
231
|
-
return None
|
|
232
|
-
|
|
233
|
-
def get_run_comments(self, run_id: UUID):
|
|
234
|
-
return sorted(ArgusTestRunComment.filter(test_run_id=run_id).all(), key=lambda c: c.posted_at)
|
|
235
|
-
|
|
236
|
-
def post_run_comment(self, test_id: UUID, run_id: UUID, message: str, reactions: dict, mentions: list[str]):
|
|
237
|
-
message_stripped = strip_html_tags(message)
|
|
238
|
-
|
|
239
|
-
mentions = set(mentions)
|
|
240
|
-
for potential_mention in re.findall(self.RE_MENTION, message_stripped):
|
|
241
|
-
if user := User.exists_by_name(potential_mention.lstrip("@")):
|
|
242
|
-
mentions.add(user) if user.id != g.user.id else None
|
|
243
|
-
|
|
244
|
-
test: ArgusTest = ArgusTest.get(id=test_id)
|
|
245
|
-
plugin = self.get_plugin(test.plugin_name)
|
|
246
|
-
release: ArgusRelease = ArgusRelease.get(id=test.release_id)
|
|
247
|
-
comment = ArgusTestRunComment()
|
|
248
|
-
comment.test_id = test.id
|
|
249
|
-
comment.message = message_stripped
|
|
250
|
-
comment.reactions = reactions
|
|
251
|
-
comment.mentions = [m.id for m in mentions]
|
|
252
|
-
comment.test_run_id = run_id
|
|
253
|
-
comment.release_id = release.id
|
|
254
|
-
comment.user_id = g.user.id
|
|
255
|
-
comment.posted_at = time.time()
|
|
256
|
-
comment.save()
|
|
257
|
-
|
|
258
|
-
run: PluginModelBase = plugin.model.get(id=run_id)
|
|
259
|
-
build_number = get_build_number(build_job_url=run.build_job_url)
|
|
260
|
-
for mention in mentions:
|
|
261
|
-
params = {
|
|
262
|
-
"username": g.user.username,
|
|
263
|
-
"run_id": comment.test_run_id,
|
|
264
|
-
"test_id": test.id,
|
|
265
|
-
"build_id": run.build_id,
|
|
266
|
-
"build_number": build_number,
|
|
267
|
-
}
|
|
268
|
-
self.notification_manager.send_notification(
|
|
269
|
-
receiver=mention.id,
|
|
270
|
-
sender=comment.user_id,
|
|
271
|
-
notification_type=ArgusNotificationTypes.Mention,
|
|
272
|
-
source_type=ArgusNotificationSourceTypes.Comment,
|
|
273
|
-
source_id=comment.id,
|
|
274
|
-
source_message=comment.message,
|
|
275
|
-
content_params=params
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentPosted, body={
|
|
279
|
-
"message": "A comment was posted by {username}",
|
|
280
|
-
"username": g.user.username
|
|
281
|
-
}, user_id=g.user.id, run_id=run_id, release_id=release.id, test_id=test.id)
|
|
282
|
-
|
|
283
|
-
return self.get_run_comments(run_id=run_id)
|
|
284
|
-
|
|
285
|
-
def delete_run_comment(self, comment_id: UUID, test_id: UUID, run_id: UUID):
|
|
286
|
-
comment: ArgusTestRunComment = ArgusTestRunComment.get(id=comment_id)
|
|
287
|
-
if comment.user_id != g.user.id:
|
|
288
|
-
raise Exception("Unable to delete other user comments")
|
|
289
|
-
comment.delete()
|
|
290
|
-
|
|
291
|
-
EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentDeleted, body={
|
|
292
|
-
"message": "A comment was deleted by {username}",
|
|
293
|
-
"username": g.user.username
|
|
294
|
-
}, user_id=g.user.id, run_id=run_id, release_id=comment.release_id, test_id=test_id)
|
|
295
|
-
|
|
296
|
-
return self.get_run_comments(run_id=run_id)
|
|
297
|
-
|
|
298
|
-
def update_run_comment(self, comment_id: UUID, test_id: UUID, run_id: UUID, message: str, mentions: list[str], reactions: dict):
|
|
299
|
-
comment: ArgusTestRunComment = ArgusTestRunComment.get(id=comment_id)
|
|
300
|
-
if comment.user_id != g.user.id:
|
|
301
|
-
raise Exception("Unable to edit other user comments")
|
|
302
|
-
comment.message = strip_html_tags(message)
|
|
303
|
-
comment.reactions = reactions
|
|
304
|
-
comment.mentions = mentions
|
|
305
|
-
comment.save()
|
|
306
|
-
|
|
307
|
-
EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentUpdated, body={
|
|
308
|
-
"message": "A comment was edited by {username}",
|
|
309
|
-
"username": g.user.username
|
|
310
|
-
}, user_id=g.user.id, run_id=run_id, release_id=comment.release_id, test_id=test_id)
|
|
311
|
-
|
|
312
|
-
return self.get_run_comments(run_id=run_id)
|
|
313
|
-
|
|
314
|
-
def get_run_events(self, run_id: UUID):
|
|
315
|
-
response = {}
|
|
316
|
-
all_events = ArgusEvent.filter(run_id=run_id).all()
|
|
317
|
-
all_events = sorted(all_events, key=lambda ev: ev.created_at)
|
|
318
|
-
response["run_id"] = run_id
|
|
319
|
-
response["raw_events"] = [dict(event.items()) for event in all_events]
|
|
320
|
-
response["events"] = {
|
|
321
|
-
str(event.id): EVENT_PROCESSORS.get(event.kind)(json.loads(event.body))
|
|
322
|
-
for event in all_events
|
|
323
|
-
}
|
|
324
|
-
return response
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
def submit_github_issue(self, issue_url: str, test_id: UUID, run_id: UUID):
|
|
328
|
-
user_tokens = UserOauthToken.filter(user_id=g.user.id).all()
|
|
329
|
-
token = None
|
|
330
|
-
for tok in user_tokens:
|
|
331
|
-
if tok.kind == "github":
|
|
332
|
-
token = tok.token
|
|
333
|
-
break
|
|
334
|
-
if not token:
|
|
335
|
-
raise Exception("Github token not found")
|
|
336
|
-
|
|
337
|
-
match = re.match(
|
|
338
|
-
r"http(s)?://(www\.)?github\.com/(?P<owner>[\w\d]+)/"
|
|
339
|
-
r"(?P<repo>[\w\d\-_]+)/(?P<type>issues|pull)/(?P<issue_number>\d+)(/)?",
|
|
340
|
-
issue_url,
|
|
341
|
-
)
|
|
342
|
-
if not match:
|
|
343
|
-
raise Exception("URL doesn't match Github schema")
|
|
344
|
-
|
|
345
|
-
test: ArgusTest = ArgusTest.get(id=test_id)
|
|
346
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
347
|
-
|
|
348
|
-
run = plugin.model.get(id=run_id)
|
|
349
|
-
release = ArgusRelease.get(id=run["release_id"])
|
|
350
|
-
test = ArgusTest.get(build_system_id=run["build_id"])
|
|
351
|
-
group = ArgusGroup.get(id=test.group_id)
|
|
352
|
-
|
|
353
|
-
new_issue = ArgusGithubIssue()
|
|
354
|
-
new_issue.user_id = g.user.id
|
|
355
|
-
new_issue.run_id = run_id
|
|
356
|
-
new_issue.group_id = group.id
|
|
357
|
-
new_issue.release_id = release.id
|
|
358
|
-
new_issue.test_id = test.id
|
|
359
|
-
new_issue.type = match.group("type")
|
|
360
|
-
new_issue.owner = match.group("owner")
|
|
361
|
-
new_issue.repo = match.group("repo")
|
|
362
|
-
new_issue.issue_number = int(match.group("issue_number"))
|
|
363
|
-
|
|
364
|
-
issue_request = requests.get(
|
|
365
|
-
f"https://api.github.com/repos/{new_issue.owner}/{new_issue.repo}/issues/{new_issue.issue_number}",
|
|
366
|
-
headers={
|
|
367
|
-
**self.github_headers,
|
|
368
|
-
"Authorization": f"token {token}",
|
|
369
|
-
}
|
|
370
|
-
)
|
|
371
|
-
if issue_request.status_code != 200:
|
|
372
|
-
raise Exception(f"Error getting issue state: Response: HTTP {issue_request.status_code}", issue_request.json())
|
|
373
|
-
|
|
374
|
-
issue_state: dict[str, Any] = issue_request.json()
|
|
375
|
-
|
|
376
|
-
new_issue.title = issue_state.get("title")
|
|
377
|
-
new_issue.url = issue_state.get("html_url")
|
|
378
|
-
new_issue.last_status = issue_state.get("state")
|
|
379
|
-
new_issue.save()
|
|
380
|
-
|
|
381
|
-
EventService.create_run_event(
|
|
382
|
-
kind=ArgusEventTypes.TestRunIssueAdded,
|
|
383
|
-
body={
|
|
384
|
-
"message": "An issue titled \"{title}\" was added by {username}",
|
|
385
|
-
"username": g.user.username,
|
|
386
|
-
"url": issue_url,
|
|
387
|
-
"title": issue_state.get("title"),
|
|
388
|
-
"state": issue_state.get("state"),
|
|
389
|
-
},
|
|
390
|
-
user_id=g.user.id,
|
|
391
|
-
run_id=new_issue.run_id,
|
|
392
|
-
release_id=new_issue.release_id,
|
|
393
|
-
group_id=new_issue.group_id,
|
|
394
|
-
test_id=new_issue.test_id
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
response = {
|
|
398
|
-
**dict(list(new_issue.items())),
|
|
399
|
-
"title": issue_state.get("title"),
|
|
400
|
-
"state": issue_state.get("state"),
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return response
|
|
404
|
-
|
|
405
|
-
def _get_github_issues_for_view(self, view_id: UUID | str) -> list[ArgusGithubIssue]:
|
|
406
|
-
view: ArgusUserView = ArgusUserView.get(id=view_id)
|
|
407
|
-
issues = []
|
|
408
|
-
for batch in chunk(view.tests):
|
|
409
|
-
issues.extend(ArgusGithubIssue.filter(test_id__in=batch).allow_filtering().all())
|
|
410
|
-
|
|
411
|
-
return issues
|
|
412
|
-
|
|
413
|
-
def get_github_issues(self, filter_key: str, filter_id: UUID, aggregate_by_issue: bool = False) -> dict:
|
|
414
|
-
if filter_key not in ["release_id", "group_id", "test_id", "run_id", "user_id", "view_id"]:
|
|
415
|
-
raise Exception(
|
|
416
|
-
"filter_key can only be one of: \"release_id\", \"group_id\", \"test_id\", \"run_id\", \"user_id\", \"view_id\""
|
|
417
|
-
)
|
|
418
|
-
if filter_key == "view_id":
|
|
419
|
-
all_issues = self._get_github_issues_for_view(filter_id)
|
|
420
|
-
else:
|
|
421
|
-
all_issues = ArgusGithubIssue.filter(**{filter_key: filter_id}).all()
|
|
422
|
-
if aggregate_by_issue:
|
|
423
|
-
runs_by_issue = {}
|
|
424
|
-
response = []
|
|
425
|
-
for issue in all_issues:
|
|
426
|
-
runs = runs_by_issue.get(issue, [])
|
|
427
|
-
runs.append({"test_id": issue.test_id, "run_id": issue.run_id})
|
|
428
|
-
runs_by_issue[issue] = runs
|
|
429
|
-
|
|
430
|
-
for issue, runs in runs_by_issue.items():
|
|
431
|
-
issue_dict = dict(issue.items())
|
|
432
|
-
issue_dict["runs"] = runs
|
|
433
|
-
response.append(issue_dict)
|
|
434
|
-
|
|
435
|
-
else:
|
|
436
|
-
response = [dict(issue.items()) for issue in all_issues]
|
|
437
|
-
return response
|
|
438
|
-
|
|
439
|
-
def resolve_run_build_id_and_number_multiple(self, runs: list[tuple[UUID, UUID]]) -> dict[UUID, dict[str, Any]]:
|
|
440
|
-
test_ids = [r[0] for r in runs]
|
|
441
|
-
all_tests: list = []
|
|
442
|
-
for id_slice in chunk(test_ids):
|
|
443
|
-
all_tests.extend(ArgusTest.filter(id__in=id_slice).all())
|
|
444
|
-
|
|
445
|
-
tests: dict[str, ArgusTest] = {str(t.id): t for t in all_tests}
|
|
446
|
-
runs_by_plugin = reduce(lambda acc, val: acc[tests[val[0]].plugin_name].append(val[1]) or acc, runs, defaultdict(list))
|
|
447
|
-
all_runs = {}
|
|
448
|
-
for plugin, run_ids in runs_by_plugin.items():
|
|
449
|
-
model = AVAILABLE_PLUGINS.get(plugin).model
|
|
450
|
-
model_runs = []
|
|
451
|
-
for run_id in run_ids:
|
|
452
|
-
model_runs.append(model.filter(id=run_id).only(["build_id", "start_time", "build_job_url", "id", "test_id"]).get())
|
|
453
|
-
all_runs.update({ str(run["id"]): {**run, "build_number": get_build_number(run["build_job_url"])} for run in model_runs })
|
|
454
|
-
|
|
455
|
-
return all_runs
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
def delete_github_issue(self, issue_id: UUID) -> dict:
|
|
459
|
-
issue: ArgusGithubIssue = ArgusGithubIssue.get(id=issue_id)
|
|
460
|
-
|
|
461
|
-
EventService.create_run_event(
|
|
462
|
-
kind=ArgusEventTypes.TestRunIssueRemoved,
|
|
463
|
-
body={
|
|
464
|
-
"message": "An issue titled \"{title}\" was removed by {username}",
|
|
465
|
-
"username": g.user.username,
|
|
466
|
-
"url": issue.url,
|
|
467
|
-
"title": issue.title,
|
|
468
|
-
"state": issue.last_status,
|
|
469
|
-
},
|
|
470
|
-
user_id=g.user.id,
|
|
471
|
-
run_id=issue.run_id,
|
|
472
|
-
release_id=issue.release_id,
|
|
473
|
-
group_id=issue.group_id,
|
|
474
|
-
test_id=issue.test_id
|
|
475
|
-
)
|
|
476
|
-
issue.delete()
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
"deleted": issue_id
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
def terminate_stuck_runs(self):
|
|
483
|
-
sct = AVAILABLE_PLUGINS.get("scylla-cluster-tests").model
|
|
484
|
-
now = datetime.utcnow()
|
|
485
|
-
stuck_period = now - timedelta(minutes=45)
|
|
486
|
-
stuck_runs_running = sct.filter(heartbeat__lt=int(
|
|
487
|
-
stuck_period.timestamp()), status=TestStatus.RUNNING.value).allow_filtering().all()
|
|
488
|
-
stuck_runs_created = sct.filter(heartbeat__lt=int(
|
|
489
|
-
stuck_period.timestamp()), status=TestStatus.CREATED.value).allow_filtering().all()
|
|
490
|
-
|
|
491
|
-
all_stuck_runs = [*stuck_runs_running, *stuck_runs_created]
|
|
492
|
-
LOGGER.info("Found %s stuck runs", len(all_stuck_runs))
|
|
493
|
-
|
|
494
|
-
for run in all_stuck_runs:
|
|
495
|
-
LOGGER.info("Will set %s as ABORTED", run.id)
|
|
496
|
-
old_status = run.status
|
|
497
|
-
run.status = TestStatus.ABORTED.value
|
|
498
|
-
run.save()
|
|
499
|
-
|
|
500
|
-
EventService.create_run_event(
|
|
501
|
-
kind=ArgusEventTypes.TestRunStatusChanged,
|
|
502
|
-
body={
|
|
503
|
-
"message": "Run was automatically terminated due to not responding for more than 45 minutes "
|
|
504
|
-
"(Status changed from {old_status} to {new_status}) by {username}",
|
|
505
|
-
"old_status": old_status,
|
|
506
|
-
"new_status": run.status,
|
|
507
|
-
"username": g.user.username
|
|
508
|
-
},
|
|
509
|
-
user_id=g.user.id,
|
|
510
|
-
run_id=run.id,
|
|
511
|
-
release_id=run.release_id,
|
|
512
|
-
group_id=run.group_id,
|
|
513
|
-
test_id=run.test_id
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
return len(all_stuck_runs)
|
|
517
|
-
|
|
518
|
-
def ignore_jobs(self, test_id: UUID, reason: str):
|
|
519
|
-
test: ArgusTest = ArgusTest.get(id=test_id)
|
|
520
|
-
plugin = self.get_plugin(plugin_name=test.plugin_name)
|
|
521
|
-
|
|
522
|
-
if not reason:
|
|
523
|
-
raise TestRunServiceException("Reason for ignore cannot be empty")
|
|
524
|
-
|
|
525
|
-
cluster = ScyllaCluster.get()
|
|
526
|
-
batch = BatchStatement(consistency_level=ConsistencyLevel.QUORUM)
|
|
527
|
-
event_batch = BatchQuery()
|
|
528
|
-
jobs_affected = 0
|
|
529
|
-
for job in plugin.model.get_jobs_meta_by_test_id(test.id):
|
|
530
|
-
if job["status"] != TestStatus.PASSED and job["investigation_status"] == TestInvestigationStatus.NOT_INVESTIGATED:
|
|
531
|
-
batch.add(
|
|
532
|
-
plugin.model.prepare_investigation_status_update_query(
|
|
533
|
-
build_id=job["build_id"],
|
|
534
|
-
start_time=job["start_time"],
|
|
535
|
-
new_status=TestInvestigationStatus.IGNORED
|
|
536
|
-
)
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
ArgusEvent.batch(event_batch).create(
|
|
540
|
-
release_id = job["release_id"],
|
|
541
|
-
group_id = job["group_id"],
|
|
542
|
-
test_id = test_id,
|
|
543
|
-
user_id = g.user.id,
|
|
544
|
-
run_id = job["id"],
|
|
545
|
-
body = json.dumps({
|
|
546
|
-
"message": "Run was marked as ignored by {username} due to the following reason: {reason}",
|
|
547
|
-
"username": g.user.username,
|
|
548
|
-
"reason": reason,
|
|
549
|
-
}, ensure_ascii=True, separators=(',', ':')),
|
|
550
|
-
kind = ArgusEventTypes.TestRunBatchInvestigationStatusChange.value,
|
|
551
|
-
created_at = datetime.utcnow(),
|
|
552
|
-
)
|
|
553
|
-
|
|
554
|
-
jobs_affected += 1
|
|
555
|
-
|
|
556
|
-
cluster.session.execute(batch)
|
|
557
|
-
event_batch.execute()
|
|
558
|
-
|
|
559
|
-
return jobs_affected
|