argus-alm 0.14.2__py3-none-any.whl → 0.15.2__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/_version.py +21 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/__init__.py +0 -0
- argus/backend/cli.py +57 -0
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +20 -0
- argus/backend/controller/admin_api.py +355 -0
- argus/backend/controller/api.py +589 -0
- argus/backend/controller/auth.py +67 -0
- argus/backend/controller/client_api.py +109 -0
- argus/backend/controller/main.py +316 -0
- argus/backend/controller/notification_api.py +72 -0
- argus/backend/controller/notifications.py +13 -0
- argus/backend/controller/planner_api.py +194 -0
- argus/backend/controller/team.py +129 -0
- argus/backend/controller/team_ui.py +19 -0
- argus/backend/controller/testrun_api.py +513 -0
- argus/backend/controller/view_api.py +188 -0
- argus/backend/controller/views_widgets/__init__.py +0 -0
- argus/backend/controller/views_widgets/graphed_stats.py +54 -0
- argus/backend/controller/views_widgets/graphs.py +68 -0
- argus/backend/controller/views_widgets/highlights.py +135 -0
- argus/backend/controller/views_widgets/nemesis_stats.py +26 -0
- argus/backend/controller/views_widgets/summary.py +43 -0
- argus/backend/db.py +98 -0
- argus/backend/error_handlers.py +41 -0
- argus/backend/events/event_processors.py +34 -0
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/argus_ai.py +24 -0
- argus/backend/models/github_issue.py +60 -0
- argus/backend/models/plan.py +24 -0
- argus/backend/models/result.py +187 -0
- argus/backend/models/runtime_store.py +58 -0
- argus/backend/models/view_widgets.py +25 -0
- argus/backend/models/web.py +403 -0
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +248 -0
- argus/backend/plugins/driver_matrix_tests/controller.py +66 -0
- argus/backend/plugins/driver_matrix_tests/model.py +429 -0
- argus/backend/plugins/driver_matrix_tests/plugin.py +21 -0
- argus/backend/plugins/driver_matrix_tests/raw_types.py +62 -0
- argus/backend/plugins/driver_matrix_tests/service.py +61 -0
- argus/backend/plugins/driver_matrix_tests/udt.py +42 -0
- argus/backend/plugins/generic/model.py +86 -0
- argus/backend/plugins/generic/plugin.py +15 -0
- argus/backend/plugins/generic/types.py +14 -0
- argus/backend/plugins/loader.py +39 -0
- argus/backend/plugins/sct/controller.py +224 -0
- argus/backend/plugins/sct/plugin.py +37 -0
- argus/backend/plugins/sct/resource_setup.py +177 -0
- argus/backend/plugins/sct/service.py +682 -0
- argus/backend/plugins/sct/testrun.py +288 -0
- argus/backend/plugins/sct/udt.py +100 -0
- argus/backend/plugins/sirenada/model.py +118 -0
- argus/backend/plugins/sirenada/plugin.py +16 -0
- argus/backend/service/admin.py +26 -0
- argus/backend/service/argus_service.py +696 -0
- argus/backend/service/build_system_monitor.py +185 -0
- argus/backend/service/client_service.py +127 -0
- argus/backend/service/event_service.py +18 -0
- argus/backend/service/github_service.py +233 -0
- argus/backend/service/jenkins_service.py +269 -0
- argus/backend/service/notification_manager.py +159 -0
- argus/backend/service/planner_service.py +608 -0
- argus/backend/service/release_manager.py +229 -0
- argus/backend/service/results_service.py +690 -0
- argus/backend/service/stats.py +610 -0
- argus/backend/service/team_manager_service.py +82 -0
- argus/backend/service/test_lookup.py +172 -0
- argus/backend/service/testrun.py +489 -0
- argus/backend/service/user.py +308 -0
- argus/backend/service/views.py +219 -0
- argus/backend/service/views_widgets/__init__.py +0 -0
- argus/backend/service/views_widgets/graphed_stats.py +180 -0
- argus/backend/service/views_widgets/highlights.py +374 -0
- argus/backend/service/views_widgets/nemesis_stats.py +34 -0
- argus/backend/template_filters.py +27 -0
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/client_service/__init__.py +0 -0
- argus/backend/tests/client_service/test_submit_results.py +79 -0
- argus/backend/tests/conftest.py +180 -0
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +178 -0
- argus/backend/tests/results_service/test_cell.py +65 -0
- argus/backend/tests/results_service/test_chartjs_additional_functions.py +259 -0
- argus/backend/tests/results_service/test_create_chartjs.py +220 -0
- argus/backend/tests/results_service/test_result_metadata.py +100 -0
- argus/backend/tests/results_service/test_results_service.py +203 -0
- argus/backend/tests/results_service/test_validation_rules.py +213 -0
- argus/backend/tests/view_widgets/__init__.py +0 -0
- argus/backend/tests/view_widgets/test_highlights_api.py +532 -0
- argus/backend/util/common.py +65 -0
- argus/backend/util/config.py +38 -0
- argus/backend/util/encoders.py +56 -0
- argus/backend/util/logsetup.py +80 -0
- argus/backend/util/module_loaders.py +30 -0
- argus/backend/util/send_email.py +91 -0
- argus/client/base.py +1 -3
- argus/client/driver_matrix_tests/cli.py +17 -8
- argus/client/generic/cli.py +4 -2
- argus/client/generic/client.py +1 -0
- argus/client/generic_result.py +48 -9
- argus/client/sct/client.py +1 -3
- argus/client/sirenada/client.py +4 -1
- argus/client/tests/__init__.py +0 -0
- argus/client/tests/conftest.py +19 -0
- argus/client/tests/test_package.py +45 -0
- argus/client/tests/test_results.py +224 -0
- argus/common/sct_types.py +3 -0
- argus/common/sirenada_types.py +1 -1
- {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info}/METADATA +43 -19
- argus_alm-0.15.2.dist-info/RECORD +122 -0
- {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info}/WHEEL +2 -1
- argus_alm-0.15.2.dist-info/entry_points.txt +3 -0
- argus_alm-0.15.2.dist-info/top_level.txt +1 -0
- argus_alm-0.14.2.dist-info/RECORD +0 -20
- argus_alm-0.14.2.dist-info/entry_points.txt +0 -4
- {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
from uuid import UUID, uuid1, uuid4
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum, IntEnum, auto
|
|
4
|
+
from cassandra.cqlengine.models import Model
|
|
5
|
+
from cassandra.cqlengine.usertype import UserType
|
|
6
|
+
from cassandra.cqlengine import columns
|
|
7
|
+
from cassandra.util import uuid_from_time, unix_time_from_uuid1
|
|
8
|
+
|
|
9
|
+
from argus.backend.models.github_issue import GithubIssue, IssueLink
|
|
10
|
+
from argus.backend.models.plan import ArgusReleasePlan
|
|
11
|
+
from argus.backend.models.result import ArgusGenericResultMetadata, ArgusGenericResultData, ArgusBestResultData, ArgusGraphView
|
|
12
|
+
from argus.backend.models.runtime_store import RuntimeStore
|
|
13
|
+
from argus.backend.models.view_widgets import WidgetHighlights, WidgetComment
|
|
14
|
+
from argus.backend.models.argus_ai import ErrorEventEmbeddings, CriticalEventEmbeddings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def uuid_now():
|
|
18
|
+
return uuid_from_time(datetime.utcnow())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ArgusTestException(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UserRoles(str, Enum):
|
|
26
|
+
User = "ROLE_USER"
|
|
27
|
+
Manager = "ROLE_MANAGER"
|
|
28
|
+
Admin = "ROLE_ADMIN"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class User(Model):
|
|
32
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
33
|
+
username = columns.Text(index=True)
|
|
34
|
+
full_name = columns.Text()
|
|
35
|
+
password = columns.Text()
|
|
36
|
+
email = columns.Text(index=True)
|
|
37
|
+
registration_date = columns.DateTime()
|
|
38
|
+
roles = columns.List(value_type=columns.Text)
|
|
39
|
+
picture_id = columns.UUID(default=None)
|
|
40
|
+
api_token = columns.Text(index=True)
|
|
41
|
+
|
|
42
|
+
def __hash__(self) -> int:
|
|
43
|
+
return hash(self.id)
|
|
44
|
+
|
|
45
|
+
def is_manager(self) -> bool:
|
|
46
|
+
return UserRoles.Manager in self.roles
|
|
47
|
+
|
|
48
|
+
def is_admin(self) -> bool:
|
|
49
|
+
return UserRoles.Admin in self.roles
|
|
50
|
+
|
|
51
|
+
def set_as_admin(self) -> None:
|
|
52
|
+
if UserRoles.Admin not in self.roles:
|
|
53
|
+
self.roles.append(UserRoles.Admin.value)
|
|
54
|
+
|
|
55
|
+
def set_as_manager(self) -> None:
|
|
56
|
+
if UserRoles.Manager not in self.roles:
|
|
57
|
+
self.roles.append(UserRoles.Manager.value)
|
|
58
|
+
|
|
59
|
+
def get_id(self):
|
|
60
|
+
return str(self.id)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def exists(cls, user_id: UUID):
|
|
64
|
+
try:
|
|
65
|
+
user = cls.get(id=user_id)
|
|
66
|
+
if user:
|
|
67
|
+
return user
|
|
68
|
+
except cls.DoesNotExist:
|
|
69
|
+
pass
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def exists_by_name(cls, name: str):
|
|
74
|
+
try:
|
|
75
|
+
user = cls.get(username=name)
|
|
76
|
+
if user:
|
|
77
|
+
return user
|
|
78
|
+
except cls.DoesNotExist:
|
|
79
|
+
pass
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
return f"User('{self.id}','{self.username}')"
|
|
84
|
+
|
|
85
|
+
def to_json(self):
|
|
86
|
+
return {
|
|
87
|
+
"id": str(self.id),
|
|
88
|
+
"username": self.username,
|
|
89
|
+
"full_name": self.full_name,
|
|
90
|
+
"picture_id": self.picture_id
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Team(Model):
|
|
95
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
96
|
+
name = columns.Text(required=True)
|
|
97
|
+
leader = columns.UUID(index=True, required=True)
|
|
98
|
+
members = columns.List(value_type=columns.UUID)
|
|
99
|
+
motd = columns.Text()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class UserOauthToken(Model):
|
|
103
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
104
|
+
user_id = columns.UUID(index=True, required=True)
|
|
105
|
+
kind = columns.Text(required=True, index=True)
|
|
106
|
+
token = columns.Text(required=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ArgusRelease(Model):
|
|
110
|
+
__table_name__ = "argus_release_v2"
|
|
111
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
112
|
+
name = columns.Text(index=True, required=True)
|
|
113
|
+
pretty_name = columns.Text()
|
|
114
|
+
description = columns.Text()
|
|
115
|
+
github_repo_url = columns.Text()
|
|
116
|
+
valid_version_regex = columns.Text()
|
|
117
|
+
assignee = columns.List(value_type=columns.UUID)
|
|
118
|
+
picture_id = columns.UUID()
|
|
119
|
+
enabled = columns.Boolean(default=lambda: True)
|
|
120
|
+
perpetual = columns.Boolean(default=lambda: False)
|
|
121
|
+
dormant = columns.Boolean(default=lambda: False)
|
|
122
|
+
|
|
123
|
+
def __eq__(self, other):
|
|
124
|
+
if isinstance(other, ArgusRelease):
|
|
125
|
+
return self.name == other.name
|
|
126
|
+
else:
|
|
127
|
+
return super().__eq__(other)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ArgusGroup(Model):
|
|
131
|
+
__table_name__ = "argus_group_v2"
|
|
132
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
133
|
+
release_id = columns.UUID(required=True, index=True)
|
|
134
|
+
name = columns.Text(required=True, index=True)
|
|
135
|
+
pretty_name = columns.Text()
|
|
136
|
+
description = columns.Text()
|
|
137
|
+
assignee = columns.List(value_type=columns.UUID)
|
|
138
|
+
build_system_id = columns.Text()
|
|
139
|
+
enabled = columns.Boolean(default=lambda: True)
|
|
140
|
+
|
|
141
|
+
def __hash__(self) -> int:
|
|
142
|
+
return hash((self.id, self.release_id))
|
|
143
|
+
|
|
144
|
+
def __eq__(self, other):
|
|
145
|
+
if isinstance(other, ArgusGroup):
|
|
146
|
+
return self.name == other.name and self.release_id == other.release_id
|
|
147
|
+
else:
|
|
148
|
+
return super().__eq__(other)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class ArgusUserView(Model):
|
|
152
|
+
id = columns.UUID(primary_key=True, partition_key=True, default=uuid4)
|
|
153
|
+
name = columns.Text(required=True, index=True)
|
|
154
|
+
display_name = columns.Text()
|
|
155
|
+
description = columns.Text()
|
|
156
|
+
user_id = columns.UUID(required=True, index=True)
|
|
157
|
+
plan_id = columns.UUID(index=True)
|
|
158
|
+
tests = columns.List(value_type=columns.UUID, default=lambda: [])
|
|
159
|
+
release_ids = columns.List(value_type=columns.UUID, default=lambda: [])
|
|
160
|
+
group_ids = columns.List(value_type=columns.UUID, default=lambda: [])
|
|
161
|
+
created = columns.DateTime(default=datetime.utcnow)
|
|
162
|
+
last_updated = columns.DateTime(default=datetime.utcnow)
|
|
163
|
+
widget_settings = columns.Text(required=True)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ArgusTest(Model):
|
|
167
|
+
__table_name__ = "argus_test_v2"
|
|
168
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
169
|
+
group_id = columns.UUID(required=True, index=True)
|
|
170
|
+
release_id = columns.UUID(required=True, index=True)
|
|
171
|
+
name = columns.Text(required=True, index=True)
|
|
172
|
+
pretty_name = columns.Text()
|
|
173
|
+
description = columns.Text()
|
|
174
|
+
assignee = columns.List(value_type=columns.UUID)
|
|
175
|
+
build_system_id = columns.Text(index=True)
|
|
176
|
+
enabled = columns.Boolean(default=lambda: True)
|
|
177
|
+
build_system_url = columns.Text()
|
|
178
|
+
plugin_name = columns.Text()
|
|
179
|
+
plugin_subtype = columns.Text()
|
|
180
|
+
|
|
181
|
+
def __eq__(self, other):
|
|
182
|
+
if isinstance(other, ArgusTest):
|
|
183
|
+
return self.name == other.name and self.group_id == other.group_id and self.release_id == other.release_id
|
|
184
|
+
else:
|
|
185
|
+
return super().__eq__(other)
|
|
186
|
+
|
|
187
|
+
def validate_build_system_id(self):
|
|
188
|
+
try:
|
|
189
|
+
t = ArgusTest.get(build_system_id=self.build_system_id)
|
|
190
|
+
if t.id != self.id:
|
|
191
|
+
raise ArgusTestException("Build Id is already used by another test", t.id, self.id)
|
|
192
|
+
except ArgusTest.DoesNotExist:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ArgusTestRunComment(Model):
|
|
197
|
+
id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
|
|
198
|
+
test_run_id = columns.UUID(required=True, index=True)
|
|
199
|
+
user_id = columns.UUID(required=True, index=True)
|
|
200
|
+
release_id = columns.UUID(required=True, index=True)
|
|
201
|
+
test_id = columns.UUID(required=True, index=True)
|
|
202
|
+
posted_at = columns.Integer(
|
|
203
|
+
required=True, clustering_order="desc", primary_key=True)
|
|
204
|
+
message = columns.Text(min_length=1, max_length=65535)
|
|
205
|
+
mentions = columns.List(value_type=columns.UUID, default=[])
|
|
206
|
+
reactions = columns.Map(key_type=columns.Text, value_type=columns.Integer)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ArgusEventTypes(str, Enum):
|
|
210
|
+
AssigneeChanged = "ARGUS_ASSIGNEE_CHANGE"
|
|
211
|
+
TestRunStatusChanged = "ARGUS_TEST_RUN_STATUS_CHANGE"
|
|
212
|
+
TestRunInvestigationStatusChanged = "ARGUS_TEST_RUN_INVESTIGATION_STATUS_CHANGE"
|
|
213
|
+
TestRunBatchInvestigationStatusChange = "ARGUS_TEST_RUN_INVESTIGATION_BATCH_STATUS_CHANGE"
|
|
214
|
+
TestRunCommentPosted = "ARGUS_TEST_RUN_COMMENT_POSTED"
|
|
215
|
+
TestRunCommentUpdated = "ARGUS_TEST_RUN_COMMENT_UPDATED"
|
|
216
|
+
TestRunCommentDeleted = "ARGUS_TEST_RUN_COMMENT_DELETED"
|
|
217
|
+
TestRunIssueAdded = "ARGUS_TEST_RUN_ISSUE_ADDED"
|
|
218
|
+
TestRunIssueRemoved = "ARGUS_TEST_RUN_ISSUE_REMOVED"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ArgusEvent(Model):
|
|
222
|
+
id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
|
|
223
|
+
release_id = columns.UUID(index=True)
|
|
224
|
+
group_id = columns.UUID(index=True)
|
|
225
|
+
test_id = columns.UUID(index=True)
|
|
226
|
+
run_id = columns.UUID(index=True)
|
|
227
|
+
user_id = columns.UUID(index=True)
|
|
228
|
+
kind = columns.Text(required=True, index=True)
|
|
229
|
+
body = columns.Text(required=True)
|
|
230
|
+
created_at = columns.DateTime(required=True)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class ArgusNotificationTypes(str, Enum):
|
|
234
|
+
Mention = "TYPE_MENTION"
|
|
235
|
+
StatusChange = "TYPE_STATUS_CHANGE"
|
|
236
|
+
AssigneeChange = "TYPE_ASSIGNEE_CHANGE"
|
|
237
|
+
ScheduleChange = "TYPE_SCHEDULE_CHANGE"
|
|
238
|
+
ViewActionItemAssignee = "TYPE_VIEW_ACTION_ITEM_ASSIGNEE"
|
|
239
|
+
ViewHighlightMention = "TYPE_VIEW_HIGHLIGHT_MENTION"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class ArgusNotificationSourceTypes(str, Enum):
|
|
243
|
+
TestRun = "TEST_RUN"
|
|
244
|
+
Schedule = "SCHEDULE"
|
|
245
|
+
Comment = "COMMENT"
|
|
246
|
+
ViewActionItem = "VIEW_ACTION_ITEM"
|
|
247
|
+
ViewHighlight = "VIEW_HIGHLIGHT"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ArgusNotificationState(IntEnum):
|
|
251
|
+
UNREAD = auto()
|
|
252
|
+
READ = auto()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class ArgusNotification(Model):
|
|
256
|
+
receiver = columns.UUID(primary_key=True, partition_key=True)
|
|
257
|
+
id = columns.TimeUUID(primary_key=True, clustering_order="DESC", default=uuid_now)
|
|
258
|
+
type = columns.Text(required=True)
|
|
259
|
+
state = columns.SmallInt(required=True, default=lambda: ArgusNotificationState.UNREAD)
|
|
260
|
+
sender = columns.UUID(required=True)
|
|
261
|
+
source_type = columns.Text(required=True)
|
|
262
|
+
source_id = columns.UUID(required=True)
|
|
263
|
+
title = columns.Text(required=True, max_length=1024)
|
|
264
|
+
content = columns.Text(required=True, max_length=65535)
|
|
265
|
+
|
|
266
|
+
def to_dict_short_summary(self) -> dict:
|
|
267
|
+
return {
|
|
268
|
+
"receiver": self.receiver,
|
|
269
|
+
"sender": self.sender,
|
|
270
|
+
"id": self.id,
|
|
271
|
+
"created": unix_time_from_uuid1(self.id) * 1000,
|
|
272
|
+
"title": self.title,
|
|
273
|
+
"state": self.state,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def to_dict(self) -> dict:
|
|
277
|
+
return {
|
|
278
|
+
"receiver": self.receiver,
|
|
279
|
+
"sender": self.sender,
|
|
280
|
+
"id": self.id,
|
|
281
|
+
"created": unix_time_from_uuid1(self.id) * 1000,
|
|
282
|
+
"title": self.title,
|
|
283
|
+
"type": self.type,
|
|
284
|
+
"content": self.content,
|
|
285
|
+
"source": self.source_type,
|
|
286
|
+
"source_id": self.source_id,
|
|
287
|
+
"state": self.state,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class ArgusGithubIssue(Model):
|
|
292
|
+
# FIXME: Deprecated. To be removed.
|
|
293
|
+
id = columns.UUID(primary_key=True, default=uuid4, partition_key=True)
|
|
294
|
+
added_on = columns.DateTime(default=datetime.utcnow)
|
|
295
|
+
release_id = columns.UUID(index=True)
|
|
296
|
+
group_id = columns.UUID(index=True)
|
|
297
|
+
test_id = columns.UUID(index=True)
|
|
298
|
+
run_id = columns.UUID(index=True)
|
|
299
|
+
user_id = columns.UUID(index=True)
|
|
300
|
+
type = columns.Text()
|
|
301
|
+
owner = columns.Text()
|
|
302
|
+
repo = columns.Text()
|
|
303
|
+
issue_number = columns.Integer()
|
|
304
|
+
last_status = columns.Text()
|
|
305
|
+
title = columns.Text()
|
|
306
|
+
url = columns.Text()
|
|
307
|
+
|
|
308
|
+
def __hash__(self) -> int:
|
|
309
|
+
return hash((self.owner, self.repo, self.issue_number))
|
|
310
|
+
|
|
311
|
+
def __eq__(self, other):
|
|
312
|
+
if isinstance(other, ArgusGithubIssue):
|
|
313
|
+
return self.owner == other.owner and self.repo == other.repo and self.issue_number == other.issue_number
|
|
314
|
+
return super().__eq__(other)
|
|
315
|
+
|
|
316
|
+
def __ne__(self, other):
|
|
317
|
+
return not self == other
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class ArgusSchedule(Model):
|
|
321
|
+
__table_name__ = "argus_schedule_v4"
|
|
322
|
+
release_id = columns.UUID(primary_key=True, required=True)
|
|
323
|
+
id = columns.TimeUUID(primary_key=True, default=uuid1, clustering_order="DESC")
|
|
324
|
+
period_start = columns.DateTime(required=True, default=datetime.utcnow)
|
|
325
|
+
period_end = columns.DateTime(required=True, primary_key=True, clustering_order="DESC")
|
|
326
|
+
tag = columns.Text(default="")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class ArgusScheduleAssignee(Model):
|
|
330
|
+
__table_name__ = "argus_schedule_user_v3"
|
|
331
|
+
assignee = columns.UUID(primary_key=True)
|
|
332
|
+
id = columns.TimeUUID(primary_key=True, default=uuid1,
|
|
333
|
+
clustering_order="DESC")
|
|
334
|
+
schedule_id = columns.TimeUUID(required=True, index=True)
|
|
335
|
+
release_id = columns.UUID(required=True)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class ArgusScheduleTest(Model):
|
|
339
|
+
__table_name__ = "argus_schedule_test_v5"
|
|
340
|
+
test_id = columns.UUID(primary_key=True, required=True)
|
|
341
|
+
id = columns.TimeUUID(primary_key=True, default=uuid1,
|
|
342
|
+
clustering_order="DESC")
|
|
343
|
+
schedule_id = columns.TimeUUID(required=True, index=True)
|
|
344
|
+
release_id = columns.UUID(partition_key=True)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class ArgusScheduleGroup(Model):
|
|
348
|
+
__table_name__ = "argus_schedule_group_v3"
|
|
349
|
+
group_id = columns.UUID(partition_key=True, required=True)
|
|
350
|
+
id = columns.TimeUUID(primary_key=True, default=uuid1,
|
|
351
|
+
clustering_order="DESC")
|
|
352
|
+
schedule_id = columns.TimeUUID(required=True, index=True)
|
|
353
|
+
release_id = columns.UUID(partition_key=True)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class ReleasePlannerComment(Model):
|
|
357
|
+
__table_name__ = "argus_planner_comment_v2"
|
|
358
|
+
release = columns.UUID(primary_key=True)
|
|
359
|
+
group = columns.UUID(primary_key=True)
|
|
360
|
+
test = columns.UUID(primary_key=True)
|
|
361
|
+
comment = columns.Text(default=lambda: "")
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class WebFileStorage(Model):
|
|
365
|
+
id = columns.UUID(primary_key=True, default=uuid4)
|
|
366
|
+
filepath = columns.Text(min_length=1)
|
|
367
|
+
filename = columns.Text(min_length=1)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
USED_MODELS: list[Model] = [
|
|
371
|
+
RuntimeStore,
|
|
372
|
+
User,
|
|
373
|
+
UserOauthToken,
|
|
374
|
+
Team,
|
|
375
|
+
WebFileStorage,
|
|
376
|
+
ArgusRelease,
|
|
377
|
+
ArgusUserView,
|
|
378
|
+
ArgusGroup,
|
|
379
|
+
ArgusTest,
|
|
380
|
+
ArgusTestRunComment,
|
|
381
|
+
ArgusEvent,
|
|
382
|
+
ReleasePlannerComment,
|
|
383
|
+
ArgusNotification,
|
|
384
|
+
ArgusSchedule,
|
|
385
|
+
ArgusScheduleAssignee,
|
|
386
|
+
ArgusScheduleGroup,
|
|
387
|
+
ArgusScheduleTest,
|
|
388
|
+
ArgusGenericResultMetadata,
|
|
389
|
+
ArgusGenericResultData,
|
|
390
|
+
ArgusBestResultData,
|
|
391
|
+
ArgusReleasePlan,
|
|
392
|
+
WidgetHighlights,
|
|
393
|
+
WidgetComment,
|
|
394
|
+
ArgusGraphView,
|
|
395
|
+
ErrorEventEmbeddings,
|
|
396
|
+
CriticalEventEmbeddings,
|
|
397
|
+
GithubIssue,
|
|
398
|
+
IssueLink,
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
USED_TYPES: list[UserType] = [
|
|
402
|
+
|
|
403
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from math import ceil
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
from time import time
|
|
6
|
+
|
|
7
|
+
from cassandra.cqlengine import columns
|
|
8
|
+
from cassandra.cqlengine.models import Model
|
|
9
|
+
from cassandra.cqlengine.usertype import UserType
|
|
10
|
+
from flask import Blueprint
|
|
11
|
+
from argus.backend.db import ScyllaCluster
|
|
12
|
+
from argus.backend.models.plan import ArgusReleasePlan
|
|
13
|
+
from argus.backend.models.web import (
|
|
14
|
+
ArgusTest,
|
|
15
|
+
ArgusGroup,
|
|
16
|
+
ArgusRelease,
|
|
17
|
+
ArgusScheduleGroup,
|
|
18
|
+
ArgusSchedule,
|
|
19
|
+
ArgusScheduleTest,
|
|
20
|
+
ArgusScheduleAssignee
|
|
21
|
+
)
|
|
22
|
+
from argus.backend.util.common import chunk
|
|
23
|
+
from argus.common.enums import TestInvestigationStatus, TestStatus
|
|
24
|
+
|
|
25
|
+
LOGGER = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PluginModelBase(Model):
|
|
29
|
+
_plugin_name = "unknown"
|
|
30
|
+
# Metadata
|
|
31
|
+
build_id = columns.Text(required=True, partition_key=True)
|
|
32
|
+
start_time = columns.DateTime(required=True, primary_key=True, clustering_order="DESC",
|
|
33
|
+
default=datetime.utcnow, custom_index=True)
|
|
34
|
+
id = columns.UUID(index=True, required=True)
|
|
35
|
+
release_id = columns.UUID(index=True)
|
|
36
|
+
group_id = columns.UUID(index=True)
|
|
37
|
+
test_id = columns.UUID(index=True)
|
|
38
|
+
assignee = columns.UUID(index=True)
|
|
39
|
+
status = columns.Text(default=lambda: TestStatus.CREATED.value)
|
|
40
|
+
investigation_status = columns.Text(default=lambda: TestInvestigationStatus.NOT_INVESTIGATED.value)
|
|
41
|
+
heartbeat = columns.Integer(default=lambda: int(time()))
|
|
42
|
+
end_time = columns.DateTime(default=lambda: datetime.utcfromtimestamp(0))
|
|
43
|
+
build_job_url = columns.Text()
|
|
44
|
+
product_version = columns.Text(index=True)
|
|
45
|
+
|
|
46
|
+
# Test Logs Collection
|
|
47
|
+
logs = columns.List(value_type=columns.Tuple(columns.Text(), columns.Text()))
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def table_name(cls) -> str:
|
|
51
|
+
return cls.__table_name__
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _stats_query(cls) -> str:
|
|
55
|
+
raise NotImplementedError()
|
|
56
|
+
|
|
57
|
+
def assign_categories(self):
|
|
58
|
+
key = self.build_id
|
|
59
|
+
try:
|
|
60
|
+
test: ArgusTest = ArgusTest.get(build_system_id=key)
|
|
61
|
+
self.release_id = test.release_id
|
|
62
|
+
self.group_id = test.group_id
|
|
63
|
+
self.test_id = test.id
|
|
64
|
+
if not test.plugin_name or test.plugin_name != self._plugin_name:
|
|
65
|
+
test.plugin_name = self._plugin_name
|
|
66
|
+
test.save()
|
|
67
|
+
except ArgusTest.DoesNotExist:
|
|
68
|
+
LOGGER.warning("Test entity missing for key \"%s\", run won't be visible until this is corrected", key)
|
|
69
|
+
|
|
70
|
+
def get_assignment(self, version: str | None = None) -> UUID | None:
|
|
71
|
+
associated_test: ArgusTest = ArgusTest.get(build_system_id=self.build_id)
|
|
72
|
+
associated_release: ArgusRelease = ArgusRelease.get(id=associated_test.release_id)
|
|
73
|
+
|
|
74
|
+
plans: list[ArgusReleasePlan] = list(ArgusReleasePlan.filter(release_id=associated_release.id))
|
|
75
|
+
|
|
76
|
+
if version:
|
|
77
|
+
plans = [plan for plan in plans if plan.target_version == version]
|
|
78
|
+
|
|
79
|
+
for plan in plans:
|
|
80
|
+
if associated_test.group_id in plan.groups:
|
|
81
|
+
return plan.assignee_mapping.get(associated_test.group_id, plan.owner)
|
|
82
|
+
if associated_test.id in plan.tests:
|
|
83
|
+
return plan.assignee_mapping.get(associated_test.id, plan.owner)
|
|
84
|
+
|
|
85
|
+
# FIXME: Legacy fallback until we fully migrate to new plans
|
|
86
|
+
return self._legacy_get_scheduled_assignee(associated_test=associated_test, associated_release=associated_release)
|
|
87
|
+
|
|
88
|
+
def get_scheduled_assignee(self) -> UUID:
|
|
89
|
+
return self.get_assignment()
|
|
90
|
+
|
|
91
|
+
def _legacy_get_scheduled_assignee(self, associated_test: ArgusTest, associated_release: ArgusRelease) -> UUID:
|
|
92
|
+
"""
|
|
93
|
+
Iterate over all schedules (groups and tests) and return first available assignee
|
|
94
|
+
"""
|
|
95
|
+
associated_group = ArgusGroup.get(id=associated_test.group_id)
|
|
96
|
+
|
|
97
|
+
scheduled_groups = ArgusScheduleGroup.filter(
|
|
98
|
+
release_id=associated_release.id,
|
|
99
|
+
group_id=associated_group.id,
|
|
100
|
+
).all()
|
|
101
|
+
|
|
102
|
+
scheduled_tests = ArgusScheduleTest.filter(
|
|
103
|
+
release_id=associated_release.id,
|
|
104
|
+
test_id=associated_test.id
|
|
105
|
+
).all()
|
|
106
|
+
|
|
107
|
+
unique_schedule_ids = {scheduled_obj.schedule_id for scheduled_obj in [
|
|
108
|
+
*scheduled_tests, *scheduled_groups]}
|
|
109
|
+
|
|
110
|
+
schedules = ArgusSchedule.filter(
|
|
111
|
+
release_id=associated_release.id,
|
|
112
|
+
id__in=tuple(unique_schedule_ids),
|
|
113
|
+
).all()
|
|
114
|
+
|
|
115
|
+
today = datetime.utcnow()
|
|
116
|
+
|
|
117
|
+
valid_schedules = []
|
|
118
|
+
for schedule in schedules:
|
|
119
|
+
if schedule.period_start <= today <= schedule.period_end:
|
|
120
|
+
valid_schedules.append(schedule)
|
|
121
|
+
|
|
122
|
+
assignees_uuids = []
|
|
123
|
+
for schedule in valid_schedules:
|
|
124
|
+
assignees = ArgusScheduleAssignee.filter(
|
|
125
|
+
schedule_id=schedule.id
|
|
126
|
+
).all()
|
|
127
|
+
assignees_uuids.extend([assignee.assignee for assignee in assignees])
|
|
128
|
+
|
|
129
|
+
return assignees_uuids[0] if len(assignees_uuids) > 0 else None
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def get_jobs_assigned_to_user(cls, user_id: str | UUID):
|
|
133
|
+
cluster = ScyllaCluster.get()
|
|
134
|
+
query = cluster.prepare("SELECT build_id, start_time, release_id, group_id, assignee, "
|
|
135
|
+
f"test_id, id, status, investigation_status, build_job_url, scylla_version FROM {cls.table_name()} WHERE assignee = ?")
|
|
136
|
+
rows = cluster.session.execute(query=query, parameters=(user_id,))
|
|
137
|
+
|
|
138
|
+
return list(rows)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def get_jobs_meta_by_test_id(cls, test_id: UUID):
|
|
142
|
+
cluster = ScyllaCluster.get()
|
|
143
|
+
query = cluster.prepare(
|
|
144
|
+
f"SELECT build_id, start_time, id, test_id, release_id, group_id, status, investigation_status FROM {cls.table_name()} WHERE test_id = ?")
|
|
145
|
+
rows = cluster.session.execute(query=query, parameters=(test_id,))
|
|
146
|
+
|
|
147
|
+
return list(rows)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def prepare_investigation_status_update_query(cls, build_id: str, start_time: datetime, new_status: TestInvestigationStatus):
|
|
151
|
+
cluster = ScyllaCluster.get()
|
|
152
|
+
query = cluster.prepare(
|
|
153
|
+
f"UPDATE {cls.table_name()} SET investigation_status = ? WHERE build_id = ? AND start_time = ?")
|
|
154
|
+
bound_query = query.bind(values=(new_status.value, build_id, start_time))
|
|
155
|
+
|
|
156
|
+
return bound_query
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def get_stats_for_release(cls, release: ArgusRelease, build_ids=list[str]):
|
|
160
|
+
cluster = ScyllaCluster.get()
|
|
161
|
+
query = cluster.prepare(cls._stats_query())
|
|
162
|
+
futures = []
|
|
163
|
+
step_size = 90
|
|
164
|
+
|
|
165
|
+
for step in range(0, ceil(len(build_ids) / step_size)):
|
|
166
|
+
start_pos = step*step_size
|
|
167
|
+
next_slice = build_ids[start_pos:start_pos+step_size]
|
|
168
|
+
futures.append(cluster.session.execute_async(query=query, parameters=(next_slice,)))
|
|
169
|
+
|
|
170
|
+
return futures
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def get_run_meta_by_build_id(cls, build_id: str, limit: int = 10):
|
|
174
|
+
cluster = ScyllaCluster.get()
|
|
175
|
+
query = cluster.prepare("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
|
|
176
|
+
f"assignee, end_time, investigation_status, heartbeat FROM {cls.table_name()} WHERE build_id = ? LIMIT ?")
|
|
177
|
+
rows = cluster.session.execute(query=query, parameters=(build_id, limit))
|
|
178
|
+
|
|
179
|
+
return list(rows)
|
|
180
|
+
|
|
181
|
+
@classmethod
|
|
182
|
+
def get_run_meta_by_run_id(cls, run_id: UUID | str):
|
|
183
|
+
cluster = ScyllaCluster.get()
|
|
184
|
+
query = cluster.prepare("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
|
|
185
|
+
f"assignee, end_time, investigation_status, heartbeat FROM {cls.table_name()} WHERE id = ?")
|
|
186
|
+
rows = cluster.session.execute(query=query, parameters=(run_id,))
|
|
187
|
+
|
|
188
|
+
return list(rows)
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def load_test_run(cls, run_id: UUID) -> 'PluginModelBase':
|
|
192
|
+
raise NotImplementedError()
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def submit_run(cls, request_data: dict) -> 'PluginModelBase':
|
|
196
|
+
raise NotImplementedError()
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def get_distinct_product_versions(cls, release: ArgusRelease) -> list[str]:
|
|
200
|
+
raise NotImplementedError()
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def get_distinct_versions_for_view(cls, tests: list[ArgusTest]) -> list[str]:
|
|
204
|
+
cluster = ScyllaCluster.get()
|
|
205
|
+
statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE build_id IN ?")
|
|
206
|
+
futures = []
|
|
207
|
+
for batch in chunk(tests):
|
|
208
|
+
futures.append(cluster.session.execute_async(query=statement,
|
|
209
|
+
parameters=([t.build_system_id for t in batch],)))
|
|
210
|
+
|
|
211
|
+
rows = []
|
|
212
|
+
for future in futures:
|
|
213
|
+
rows.extend(future.result())
|
|
214
|
+
unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
|
|
215
|
+
|
|
216
|
+
return sorted(list(unique_versions), reverse=True)
|
|
217
|
+
|
|
218
|
+
def update_heartbeat(self):
|
|
219
|
+
self.heartbeat = int(time())
|
|
220
|
+
|
|
221
|
+
def change_status(self, new_status: TestStatus):
|
|
222
|
+
self.status = new_status.value
|
|
223
|
+
|
|
224
|
+
def change_investigation_status(self, new_investigation_status: TestInvestigationStatus):
|
|
225
|
+
self.investigation_status = new_investigation_status.value
|
|
226
|
+
|
|
227
|
+
def submit_product_version(self, version: str):
|
|
228
|
+
raise NotImplementedError()
|
|
229
|
+
|
|
230
|
+
def set_full_version(self, version: str):
|
|
231
|
+
self.product_version = version
|
|
232
|
+
|
|
233
|
+
def submit_logs(self, logs: list[dict]):
|
|
234
|
+
raise NotImplementedError()
|
|
235
|
+
|
|
236
|
+
def finish_run(self, payload: dict = None):
|
|
237
|
+
raise NotImplementedError()
|
|
238
|
+
|
|
239
|
+
def sut_timestamp(self, sut_package_name) -> float:
|
|
240
|
+
raise NotImplementedError()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class PluginInfoBase:
|
|
244
|
+
name: str
|
|
245
|
+
controller: Blueprint
|
|
246
|
+
model: PluginModelBase
|
|
247
|
+
all_models: list[Model]
|
|
248
|
+
all_types: list[UserType]
|