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,608 @@
|
|
|
1
|
+
|
|
2
|
+
import logging
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from functools import reduce
|
|
9
|
+
from typing import Any, Optional, TypedDict
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
from flask import g
|
|
12
|
+
from slugify import slugify
|
|
13
|
+
|
|
14
|
+
from argus.backend.models.plan import ArgusReleasePlan
|
|
15
|
+
from argus.backend.models.web import ArgusGroup, ArgusRelease, ArgusTest, ArgusUserView, User
|
|
16
|
+
from argus.backend.service.jenkins_service import JenkinsService
|
|
17
|
+
from argus.backend.service.test_lookup import TestLookup
|
|
18
|
+
from argus.backend.service.views import UserViewService
|
|
19
|
+
from argus.backend.util.common import chunk
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, init=True, repr=True, kw_only=True)
|
|
26
|
+
class CreatePlanPayload:
|
|
27
|
+
name: str
|
|
28
|
+
description: str
|
|
29
|
+
owner: str
|
|
30
|
+
participants: list[str]
|
|
31
|
+
target_version: str | None
|
|
32
|
+
release_id: str
|
|
33
|
+
tests: list[str]
|
|
34
|
+
groups: list[str]
|
|
35
|
+
assignments: dict[str, str]
|
|
36
|
+
view_id: Optional[str] = None
|
|
37
|
+
created_from: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, init=True, repr=True, kw_only=True)
|
|
41
|
+
class TempPlanPayload:
|
|
42
|
+
id: str
|
|
43
|
+
name: str
|
|
44
|
+
completed: bool
|
|
45
|
+
description: str
|
|
46
|
+
owner: str
|
|
47
|
+
participants: list[str]
|
|
48
|
+
target_version: str
|
|
49
|
+
assignee_mapping: dict[str, str]
|
|
50
|
+
assignments: dict[str, str] = None
|
|
51
|
+
release_id: str
|
|
52
|
+
tests: list[str]
|
|
53
|
+
groups: list[str]
|
|
54
|
+
creation_time: str
|
|
55
|
+
last_updated: str
|
|
56
|
+
ends_at: str
|
|
57
|
+
created_from: Optional[str]
|
|
58
|
+
view_id: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, init=True, repr=True, kw_only=True)
|
|
62
|
+
class CopyPlanPayload:
|
|
63
|
+
plan: TempPlanPayload
|
|
64
|
+
keepParticipants: bool
|
|
65
|
+
replacements: dict[str, str]
|
|
66
|
+
targetReleaseId: str
|
|
67
|
+
targetReleaseName: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PlanTriggerPayload(TypedDict):
|
|
71
|
+
plan_id: str | None
|
|
72
|
+
release: str | None
|
|
73
|
+
version: str | None
|
|
74
|
+
common_params: dict[str, str]
|
|
75
|
+
params: list[dict[str, str]]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class PlannerServiceException(Exception):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PlanningService:
|
|
83
|
+
|
|
84
|
+
VIEW_WIDGET_SETTINGS = [
|
|
85
|
+
{
|
|
86
|
+
"position": 1,
|
|
87
|
+
"type": "githubIssues",
|
|
88
|
+
"filter": [],
|
|
89
|
+
"settings": {
|
|
90
|
+
"submitDisabled": True,
|
|
91
|
+
"aggregateByIssue": True
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"position": 2,
|
|
96
|
+
"type": "releaseStats",
|
|
97
|
+
"filter": [],
|
|
98
|
+
"settings": {
|
|
99
|
+
"horizontal": False,
|
|
100
|
+
"displayExtendedStats": True,
|
|
101
|
+
"hiddenStatuses": ["not_run", "not_planned"]
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"position": 3,
|
|
106
|
+
"type": "testDashboard",
|
|
107
|
+
"filter": [],
|
|
108
|
+
"settings": {
|
|
109
|
+
"targetVersion": True,
|
|
110
|
+
"versionsIncludeNoVersion": False,
|
|
111
|
+
"productVersion": None
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
def version(self):
|
|
117
|
+
return "v1"
|
|
118
|
+
|
|
119
|
+
def create_plan(self, payload: dict[str, Any]) -> ArgusReleasePlan:
|
|
120
|
+
plan_request = CreatePlanPayload(**payload)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
existing = ArgusReleasePlan.filter(
|
|
124
|
+
name=plan_request.name, target_version=plan_request.target_version).allow_filtering().get()
|
|
125
|
+
if existing:
|
|
126
|
+
raise PlannerServiceException(
|
|
127
|
+
f"Found existing plan {existing.name} ({existing.target_version}) with the same name and version", existing, plan_request)
|
|
128
|
+
except ArgusReleasePlan.DoesNotExist:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
plan = ArgusReleasePlan()
|
|
132
|
+
plan.name = plan_request.name
|
|
133
|
+
plan.description = plan_request.description
|
|
134
|
+
plan.owner = UUID(plan_request.owner)
|
|
135
|
+
plan.target_version = plan_request.target_version
|
|
136
|
+
plan.release_id = UUID(plan_request.release_id)
|
|
137
|
+
plan.participants = plan_request.participants
|
|
138
|
+
plan.assignee_mapping = {UUID(entity_id): UUID(user_id)
|
|
139
|
+
for entity_id, user_id in plan_request.assignments.items()}
|
|
140
|
+
plan.groups = plan_request.groups
|
|
141
|
+
plan.tests = plan_request.tests
|
|
142
|
+
if plan_request.created_from:
|
|
143
|
+
plan.created_from = plan_request.created_from
|
|
144
|
+
if not plan_request.view_id:
|
|
145
|
+
view = self.create_view_for_plan(plan)
|
|
146
|
+
plan.view_id = view.id
|
|
147
|
+
else:
|
|
148
|
+
plan.view_id = plan_request.view_id
|
|
149
|
+
view = self.update_view_for_plan(plan, existing=True)
|
|
150
|
+
|
|
151
|
+
plan.save()
|
|
152
|
+
|
|
153
|
+
return plan
|
|
154
|
+
|
|
155
|
+
def update_plan(self, payload: dict[str, Any]) -> bool:
|
|
156
|
+
plan_request = TempPlanPayload(**payload)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
existing = ArgusReleasePlan.filter(
|
|
160
|
+
name=plan_request.name, target_version=plan_request.target_version).allow_filtering().get()
|
|
161
|
+
if existing and existing.id != UUID(plan_request.id):
|
|
162
|
+
raise PlannerServiceException(
|
|
163
|
+
f"Found existing plan {existing.name} ({existing.target_version}) with the same name and version", existing, plan_request)
|
|
164
|
+
except ArgusReleasePlan.DoesNotExist:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
plan: ArgusReleasePlan = ArgusReleasePlan.get(id=plan_request.id)
|
|
168
|
+
plan.owner = plan_request.owner
|
|
169
|
+
plan.participants = plan_request.participants
|
|
170
|
+
plan.assignee_mapping = plan_request.assignee_mapping
|
|
171
|
+
plan.tests = plan_request.tests
|
|
172
|
+
plan.groups = plan_request.groups
|
|
173
|
+
plan.name = plan_request.name
|
|
174
|
+
plan.target_version = plan_request.target_version
|
|
175
|
+
plan.description = plan_request.description
|
|
176
|
+
plan.last_updated = datetime.datetime.now(tz=datetime.UTC)
|
|
177
|
+
|
|
178
|
+
if plan_request.view_id:
|
|
179
|
+
if plan_request.view_id != plan.view_id:
|
|
180
|
+
try:
|
|
181
|
+
old_view: ArgusUserView = ArgusUserView.get(id=plan.view_id)
|
|
182
|
+
old_view.plan_id = None
|
|
183
|
+
old_view.save()
|
|
184
|
+
except ArgusUserView.DoesNotExist:
|
|
185
|
+
pass
|
|
186
|
+
plan.view_id = plan_request.view_id
|
|
187
|
+
view = self.update_view_for_plan(plan, existing=True)
|
|
188
|
+
else:
|
|
189
|
+
if plan.view_id:
|
|
190
|
+
view: ArgusUserView = ArgusUserView.get(id=plan.view_id)
|
|
191
|
+
view.plan_id = None
|
|
192
|
+
view.save()
|
|
193
|
+
view = self.create_view_for_plan(plan)
|
|
194
|
+
plan.view_id = view.id
|
|
195
|
+
|
|
196
|
+
plan.save()
|
|
197
|
+
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def update_view_for_plan(self, plan: ArgusReleasePlan, existing: bool = False) -> ArgusUserView:
|
|
201
|
+
service = UserViewService()
|
|
202
|
+
release: ArgusRelease = ArgusRelease.get(id=plan.release_id)
|
|
203
|
+
|
|
204
|
+
version_str = f" ({plan.target_version}) " if plan.target_version else ""
|
|
205
|
+
view_name = f"{release.name} {version_str}- {plan.name}"
|
|
206
|
+
|
|
207
|
+
view: ArgusUserView = ArgusUserView.get(id=plan.view_id)
|
|
208
|
+
if view.plan_id and view.plan_id != plan.id:
|
|
209
|
+
raise PlannerServiceException("This view is already assigned to another plan.")
|
|
210
|
+
view.plan_id = plan.id
|
|
211
|
+
settings = json.loads(view.widget_settings)
|
|
212
|
+
items = [f"test:{tid}" for tid in plan.tests]
|
|
213
|
+
items = [*items, *[f"group:{gid}" for gid in plan.groups]]
|
|
214
|
+
entities = service.parse_view_entity_list(items)
|
|
215
|
+
view.tests = entities["tests"]
|
|
216
|
+
if not existing:
|
|
217
|
+
view.display_name = view_name
|
|
218
|
+
view.name = slugify(view_name)
|
|
219
|
+
view.description = f"{plan.target_version or ''} Automatic view for the release plan \"{plan.name}\". {plan.description}"
|
|
220
|
+
view.group_ids = entities["group"]
|
|
221
|
+
|
|
222
|
+
dash = next(filter(lambda widget: widget["type"] == "testDashboard", settings), None)
|
|
223
|
+
if dash:
|
|
224
|
+
dash["settings"]["productVersion"] = plan.target_version
|
|
225
|
+
dash["settings"]["targetVersion"] = bool(plan.target_version)
|
|
226
|
+
|
|
227
|
+
view.widget_settings = json.dumps(settings)
|
|
228
|
+
view.save()
|
|
229
|
+
service.refresh_stale_view(view)
|
|
230
|
+
return view
|
|
231
|
+
|
|
232
|
+
def create_view_for_plan(self, plan: ArgusReleasePlan) -> ArgusUserView:
|
|
233
|
+
service = UserViewService()
|
|
234
|
+
release: ArgusRelease = ArgusRelease.get(id=plan.release_id)
|
|
235
|
+
items = [f"test:{tid}" for tid in plan.tests]
|
|
236
|
+
items = [*items, *[f"group:{gid}" for gid in plan.groups]]
|
|
237
|
+
version_str = f" ({plan.target_version}) " if plan.target_version else ""
|
|
238
|
+
view_name = f"{release.name} {version_str}- {plan.name}"
|
|
239
|
+
settings = deepcopy(self.VIEW_WIDGET_SETTINGS)
|
|
240
|
+
if plan.target_version:
|
|
241
|
+
settings[2]["settings"]["productVersion"] = plan.target_version
|
|
242
|
+
else:
|
|
243
|
+
settings[2]["settings"]["targetVersion"] = False
|
|
244
|
+
view = service.create_view(
|
|
245
|
+
name=slugify(view_name),
|
|
246
|
+
display_name=view_name,
|
|
247
|
+
description=f"{plan.target_version or ''} Automatic view for the release plan \"{plan.name}\". {plan.description}",
|
|
248
|
+
items=items,
|
|
249
|
+
plan_id=plan.id,
|
|
250
|
+
widget_settings=json.dumps(settings),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
view.save()
|
|
254
|
+
service.refresh_stale_view(view)
|
|
255
|
+
return view
|
|
256
|
+
|
|
257
|
+
def change_plan_owner(self, plan_id: UUID | str, new_owner: UUID | str) -> bool:
|
|
258
|
+
user: User = User.get(id=new_owner)
|
|
259
|
+
plan: ArgusReleasePlan = ArgusReleasePlan.get(id=plan_id)
|
|
260
|
+
|
|
261
|
+
plan.owner = user.id
|
|
262
|
+
plan.last_updated = datetime.datetime.now(tz=datetime.UTC)
|
|
263
|
+
|
|
264
|
+
plan.save()
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
def get_plan(self, plan_id: str | UUID) -> ArgusReleasePlan:
|
|
268
|
+
return ArgusReleasePlan.get(id=plan_id)
|
|
269
|
+
|
|
270
|
+
def get_gridview_for_release(self, release_id: str | UUID) -> dict[str, dict]:
|
|
271
|
+
release = ArgusRelease.get(id=release_id)
|
|
272
|
+
release = TestLookup.index_mapper(release, "release")
|
|
273
|
+
groups: list[ArgusGroup] = list(ArgusGroup.filter(release_id=release_id).all())
|
|
274
|
+
tests: list[ArgusTest] = list(ArgusTest.filter(release_id=release_id).all())
|
|
275
|
+
|
|
276
|
+
groups = {str(g.id): TestLookup.index_mapper(g, "group") for g in groups if g.enabled}
|
|
277
|
+
|
|
278
|
+
tests_by_group = reduce(lambda acc, test: acc[str(test.group_id)].append(test) or acc, tests, defaultdict(list))
|
|
279
|
+
|
|
280
|
+
res = {
|
|
281
|
+
"tests": {str(t.id): TestLookup.index_mapper(t) for t in tests if t.enabled and groups.get(str(t.group_id), {}).get("enabled", False)},
|
|
282
|
+
"groups": groups,
|
|
283
|
+
"testByGroup": tests_by_group
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for group in res["groups"].values():
|
|
287
|
+
group["release"] = release["name"]
|
|
288
|
+
|
|
289
|
+
for test in res["tests"].values():
|
|
290
|
+
g = res["groups"][str(test["group_id"])]
|
|
291
|
+
test["group"] = g["pretty_name"] or g["name"]
|
|
292
|
+
test["release"] = release["name"]
|
|
293
|
+
|
|
294
|
+
return res
|
|
295
|
+
|
|
296
|
+
def copy_plan(self, payload: CopyPlanPayload) -> ArgusReleasePlan:
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
existing = ArgusReleasePlan.filter(
|
|
300
|
+
name=payload.plan.name, target_version=payload.plan.target_version).allow_filtering().get()
|
|
301
|
+
if existing:
|
|
302
|
+
raise PlannerServiceException(
|
|
303
|
+
f"Found existing plan {existing.name} ({existing.target_version}) with the same name and version", existing, payload)
|
|
304
|
+
except ArgusReleasePlan.DoesNotExist:
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
original_plan: ArgusReleasePlan = ArgusReleasePlan.get(id=payload.plan.id)
|
|
308
|
+
target_release: ArgusRelease = ArgusRelease.get(id=payload.targetReleaseId)
|
|
309
|
+
original_release: ArgusRelease = ArgusRelease.get(id=original_plan.release_id)
|
|
310
|
+
|
|
311
|
+
original_tests: list[ArgusTest] = ArgusTest.filter(id__in=original_plan.tests).all()
|
|
312
|
+
original_groups: list[ArgusGroup] = ArgusGroup.filter(id__in=original_plan.groups).all()
|
|
313
|
+
target_tests: list[ArgusTest] = ArgusTest.filter(release_id=target_release.id).all()
|
|
314
|
+
target_groups: list[ArgusGroup] = ArgusGroup.filter(release_id=target_release.id).all()
|
|
315
|
+
|
|
316
|
+
tests_by_build_id = {t.build_system_id: t for t in target_tests}
|
|
317
|
+
groups_by_build_id = {g.build_system_id: g for g in target_groups}
|
|
318
|
+
|
|
319
|
+
new_tests = []
|
|
320
|
+
new_groups = []
|
|
321
|
+
new_assignee_mapping = {}
|
|
322
|
+
|
|
323
|
+
for test in original_tests:
|
|
324
|
+
original_assignee = original_plan.assignee_mapping.get(test.id)
|
|
325
|
+
new_build_id = test.build_system_id.replace(original_release.name, target_release.name, 1)
|
|
326
|
+
new_test = tests_by_build_id.get(new_build_id)
|
|
327
|
+
new_test_id = new_test.id if new_test else payload.replacements.get(test.id)
|
|
328
|
+
if new_test_id:
|
|
329
|
+
new_tests.append(new_test_id)
|
|
330
|
+
if original_assignee and payload.keepParticipants:
|
|
331
|
+
new_assignee_mapping[new_test_id] = original_assignee
|
|
332
|
+
|
|
333
|
+
for group in original_groups:
|
|
334
|
+
original_assignee = original_plan.assignee_mapping.get(group.id)
|
|
335
|
+
new_build_id = group.build_system_id.replace(original_release.name, target_release.name, 1)
|
|
336
|
+
new_group = groups_by_build_id.get(new_build_id)
|
|
337
|
+
new_group_id = new_group.id if new_group else payload.replacements.get(group.id)
|
|
338
|
+
if new_group_id:
|
|
339
|
+
new_groups.append(new_group_id)
|
|
340
|
+
if original_assignee and payload.keepParticipants:
|
|
341
|
+
new_assignee_mapping[new_group_id] = original_assignee
|
|
342
|
+
|
|
343
|
+
new_plan = ArgusReleasePlan()
|
|
344
|
+
new_plan.release_id = target_release.id
|
|
345
|
+
new_plan.owner = payload.plan.owner
|
|
346
|
+
new_plan.name = payload.plan.name
|
|
347
|
+
new_plan.description = payload.plan.description
|
|
348
|
+
if payload.keepParticipants:
|
|
349
|
+
new_plan.participants = payload.plan.participants
|
|
350
|
+
new_plan.assignee_mapping = new_assignee_mapping
|
|
351
|
+
new_plan.tests = new_tests
|
|
352
|
+
new_plan.groups = new_groups
|
|
353
|
+
new_plan.target_version = payload.plan.target_version
|
|
354
|
+
view = self.create_view_for_plan(new_plan)
|
|
355
|
+
new_plan.view_id = view.id
|
|
356
|
+
|
|
357
|
+
new_plan.save()
|
|
358
|
+
|
|
359
|
+
return new_plan
|
|
360
|
+
|
|
361
|
+
def check_plan_copy_eligibility(self, plan_id: str | UUID, target_release_id: str | UUID) -> dict:
|
|
362
|
+
target_release: ArgusRelease = ArgusRelease.get(id=target_release_id)
|
|
363
|
+
plan: ArgusReleasePlan = ArgusReleasePlan.get(id=plan_id)
|
|
364
|
+
original_release: ArgusRelease = ArgusRelease.get(id=plan.release_id)
|
|
365
|
+
|
|
366
|
+
original_tests: list[ArgusTest] = ArgusTest.filter(id__in=plan.tests).all()
|
|
367
|
+
original_groups: list[ArgusGroup] = ArgusGroup.filter(id__in=plan.groups).all()
|
|
368
|
+
|
|
369
|
+
target_tests: list[ArgusTest] = ArgusTest.filter(release_id=target_release.id).all()
|
|
370
|
+
target_groups: list[ArgusGroup] = ArgusGroup.filter(release_id=target_release.id).all()
|
|
371
|
+
|
|
372
|
+
tests_by_build_id = {t.build_system_id: t for t in target_tests}
|
|
373
|
+
groups_by_build_id = {g.build_system_id: g for g in target_groups}
|
|
374
|
+
|
|
375
|
+
missing_tests = []
|
|
376
|
+
missing_groups = []
|
|
377
|
+
status = "passed"
|
|
378
|
+
for test in original_tests:
|
|
379
|
+
new_build_id = test.build_system_id.replace(original_release.name, target_release.name, 1)
|
|
380
|
+
new_group = tests_by_build_id.get(new_build_id)
|
|
381
|
+
if not new_group:
|
|
382
|
+
t = TestLookup.index_mapper(test)
|
|
383
|
+
t["release"] = original_release.name
|
|
384
|
+
missing_tests.append(t)
|
|
385
|
+
|
|
386
|
+
for group in original_groups:
|
|
387
|
+
new_build_id = group.build_system_id.replace(original_release.name, target_release.name, 1)
|
|
388
|
+
new_group = groups_by_build_id.get(new_build_id)
|
|
389
|
+
if not new_group:
|
|
390
|
+
g = TestLookup.index_mapper(group)
|
|
391
|
+
g["release"] = original_release.name
|
|
392
|
+
missing_groups.append(g)
|
|
393
|
+
|
|
394
|
+
if len(missing_tests) > 0 or len(missing_groups) > 0:
|
|
395
|
+
status = "failed"
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
"status": status,
|
|
399
|
+
"targetRelease": target_release,
|
|
400
|
+
"originalRelease": original_release,
|
|
401
|
+
"missing": {
|
|
402
|
+
"tests": missing_tests,
|
|
403
|
+
"groups": missing_groups,
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
def release_planner(self, release_name: str) -> dict[str, Any]:
|
|
408
|
+
release: ArgusRelease = ArgusRelease.get(name=release_name)
|
|
409
|
+
|
|
410
|
+
plans: list[ArgusReleasePlan] = self.get_plans_for_release(release.id)
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
"release": release,
|
|
414
|
+
"plans": plans,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
def get_plans_for_release(self, release_id: str | UUID) -> list[ArgusReleasePlan]:
|
|
418
|
+
return list(ArgusReleasePlan.filter(release_id=release_id).all())
|
|
419
|
+
|
|
420
|
+
def delete_plan(self, plan_id: str | UUID, delete_view: bool = True):
|
|
421
|
+
plan: ArgusReleasePlan = ArgusReleasePlan.get(id=plan_id)
|
|
422
|
+
if plan.view_id:
|
|
423
|
+
view: ArgusUserView = ArgusUserView.get(id=plan.view_id)
|
|
424
|
+
if delete_view:
|
|
425
|
+
view.delete()
|
|
426
|
+
else:
|
|
427
|
+
view.plan_id = None
|
|
428
|
+
view.save()
|
|
429
|
+
|
|
430
|
+
plan.delete()
|
|
431
|
+
return True
|
|
432
|
+
|
|
433
|
+
def get_assignee_for_test(self, test_id: str | UUID, target_version: str = None) -> UUID | None:
|
|
434
|
+
dml = ArgusReleasePlan.filter(tests__contains=test_id, complete=False)
|
|
435
|
+
if target_version:
|
|
436
|
+
dml.filter(target_version=target_version)
|
|
437
|
+
potential_plans: list[ArgusReleasePlan] = dml.allow_filtering().all()
|
|
438
|
+
for plan in potential_plans:
|
|
439
|
+
# Use the most recent plan
|
|
440
|
+
return plan.assignee_mapping.get(test_id, plan.owner)
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
def get_assignee_for_group(self, group_id: str | UUID, target_version: str = None) -> UUID | None:
|
|
444
|
+
dml = ArgusReleasePlan.filter(groups__contains=group_id, complete=False)
|
|
445
|
+
if target_version:
|
|
446
|
+
dml.filter(target_version=target_version)
|
|
447
|
+
potential_plans: list[ArgusReleasePlan] = dml.allow_filtering().all()
|
|
448
|
+
for plan in potential_plans:
|
|
449
|
+
# Use the most recent plan
|
|
450
|
+
return plan.assignee_mapping.get(group_id, plan.owner)
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
def get_assignments_for_groups(self, release_id: str | UUID, version: str = None, plan_id: UUID = None) -> dict[str, UUID]:
|
|
454
|
+
release: ArgusRelease = ArgusRelease.get(id=release_id)
|
|
455
|
+
if not plan_id:
|
|
456
|
+
plans: list[ArgusReleasePlan] = list(ArgusReleasePlan.filter(release_id=release.id).all())
|
|
457
|
+
plans = plans if not version else [plan for plan in plans if plan.target_version == version]
|
|
458
|
+
else:
|
|
459
|
+
plans = [ArgusReleasePlan.get(id=plan_id)]
|
|
460
|
+
|
|
461
|
+
all_assignments = {}
|
|
462
|
+
for plan in reversed(plans):
|
|
463
|
+
# TODO: (gid, [user_id]) Should be changed to gid, user_id once old scheduling mechanism is completely removed
|
|
464
|
+
all_assignments.update(map(lambda group_id: (
|
|
465
|
+
str(group_id), [plan.assignee_mapping.get(group_id, plan.owner)]), plan.groups))
|
|
466
|
+
|
|
467
|
+
return all_assignments
|
|
468
|
+
|
|
469
|
+
def get_assignments_for_tests(self, group_id: str | UUID, version: str = None, plan_id: UUID | str = None) -> dict[str, UUID]:
|
|
470
|
+
group: ArgusGroup = ArgusGroup.get(id=group_id)
|
|
471
|
+
release: ArgusRelease = ArgusRelease.get(id=group.release_id)
|
|
472
|
+
if not plan_id:
|
|
473
|
+
plans: list[ArgusReleasePlan] = list(ArgusReleasePlan.filter(release_id=release.id).all())
|
|
474
|
+
plans = plans if not version else [plan for plan in plans if plan.target_version == version]
|
|
475
|
+
else:
|
|
476
|
+
plans = [ArgusReleasePlan.get(id=plan_id)]
|
|
477
|
+
|
|
478
|
+
all_assignments = {}
|
|
479
|
+
|
|
480
|
+
def get_assignee(test_id: UUID, mapping: dict[UUID, UUID]):
|
|
481
|
+
test_assignment = mapping.get(test_id)
|
|
482
|
+
return test_assignment
|
|
483
|
+
|
|
484
|
+
for plan in reversed(plans):
|
|
485
|
+
# TODO: (tid, [user_id]) Should be changed to tid, user_id once old scheduling mechanism is completely removed
|
|
486
|
+
all_assignments.update(map(lambda test_id: (str(test_id), [get_assignee(
|
|
487
|
+
test_id, plan.assignee_mapping) or plan.owner]), plan.tests))
|
|
488
|
+
|
|
489
|
+
return all_assignments
|
|
490
|
+
|
|
491
|
+
def complete_plan(self, plan_id: str | UUID) -> bool:
|
|
492
|
+
plan: ArgusReleasePlan = ArgusReleasePlan(id=plan_id).get()
|
|
493
|
+
plan.completed = True
|
|
494
|
+
|
|
495
|
+
plan.save()
|
|
496
|
+
return plan.completed
|
|
497
|
+
|
|
498
|
+
def resolve_plan(self, plan_id: str | UUID) -> list[dict[str, Any]]:
|
|
499
|
+
plan: ArgusReleasePlan = ArgusReleasePlan.get(id=plan_id)
|
|
500
|
+
|
|
501
|
+
release: ArgusRelease = ArgusRelease.get(id=plan.release_id)
|
|
502
|
+
tests: list[ArgusTest] = []
|
|
503
|
+
for batch in chunk(plan.tests):
|
|
504
|
+
tests.extend(ArgusTest.filter(id__in=batch).all())
|
|
505
|
+
test_groups: list[ArgusGroup] = ArgusGroup.filter(id__in=list({t.group_id for t in tests})).all()
|
|
506
|
+
test_groups = {g.id: g for g in test_groups}
|
|
507
|
+
groups: list[ArgusGroup] = list(ArgusGroup.filter(id__in=plan.groups).all())
|
|
508
|
+
|
|
509
|
+
mapped = [TestLookup.index_mapper(entity, "group" if isinstance(
|
|
510
|
+
entity, ArgusGroup) else "test") for entity in [*tests, *groups]]
|
|
511
|
+
|
|
512
|
+
for ent in mapped:
|
|
513
|
+
ent["release"] = release.name
|
|
514
|
+
if group_id := ent.get("group_id"):
|
|
515
|
+
group = test_groups.get(group_id)
|
|
516
|
+
ent["group"] = group.pretty_name or group.name
|
|
517
|
+
|
|
518
|
+
return mapped
|
|
519
|
+
|
|
520
|
+
def trigger_jobs(self, payload: PlanTriggerPayload) -> bool:
|
|
521
|
+
|
|
522
|
+
release_name = payload.get("release")
|
|
523
|
+
plan_id = payload.get("plan_id")
|
|
524
|
+
version = payload.get("version")
|
|
525
|
+
|
|
526
|
+
condition_set = (bool(release_name), bool(plan_id), bool(version))
|
|
527
|
+
|
|
528
|
+
match condition_set:
|
|
529
|
+
case (True, False, False):
|
|
530
|
+
release = ArgusRelease.get(name=release_name)
|
|
531
|
+
filter_expr = {"release_id__eq": release.id}
|
|
532
|
+
case (False, True, False):
|
|
533
|
+
filter_expr = {"id__eq": plan_id}
|
|
534
|
+
case (False, False, True):
|
|
535
|
+
filter_expr = {"target_version__eq": version}
|
|
536
|
+
case (True, False, True):
|
|
537
|
+
release = ArgusRelease.get(name=release_name)
|
|
538
|
+
filter_expr = {"target_version__eq": version, "release_id__eq": release.id}
|
|
539
|
+
case _:
|
|
540
|
+
raise PlannerServiceException("No version, release name or plan id specified.", payload)
|
|
541
|
+
|
|
542
|
+
plans: list[ArgusReleasePlan] = list(ArgusReleasePlan.filter(**filter_expr).allow_filtering().all())
|
|
543
|
+
|
|
544
|
+
if len(plans) == 0:
|
|
545
|
+
return False, "No plans to trigger"
|
|
546
|
+
|
|
547
|
+
common_params = payload.get("common_params", {})
|
|
548
|
+
params = payload.get("params", [])
|
|
549
|
+
test_ids = [test_id for plan in plans for test_id in plan.tests]
|
|
550
|
+
group_ids = [group_id for plan in plans for group_id in plan.groups]
|
|
551
|
+
|
|
552
|
+
tests = []
|
|
553
|
+
for batch in chunk(test_ids):
|
|
554
|
+
tests.extend(ArgusTest.filter(id__in=batch).all())
|
|
555
|
+
|
|
556
|
+
for batch in (chunk(group_ids)):
|
|
557
|
+
tests.extend(ArgusTest.filter(group_id__in=batch).allow_filtering().all())
|
|
558
|
+
|
|
559
|
+
tests = list({test for test in tests})
|
|
560
|
+
|
|
561
|
+
LOGGER.info("Will trigger %s tests...", len(tests))
|
|
562
|
+
|
|
563
|
+
service = JenkinsService()
|
|
564
|
+
failures = []
|
|
565
|
+
successes = []
|
|
566
|
+
for test in tests:
|
|
567
|
+
try:
|
|
568
|
+
latest_build_number = service.latest_build(test.build_system_id)
|
|
569
|
+
if latest_build_number == -1:
|
|
570
|
+
failures.append(test.build_system_id)
|
|
571
|
+
continue
|
|
572
|
+
raw_params = service.retrieve_job_parameters(test.build_system_id, latest_build_number)
|
|
573
|
+
job_params = {param["name"]: param["value"] for param in raw_params if param.get("value")}
|
|
574
|
+
backend = job_params.get("backend")
|
|
575
|
+
match backend.split("-"):
|
|
576
|
+
case ["aws", *_]:
|
|
577
|
+
region_key = "region"
|
|
578
|
+
case ["gce", *_]:
|
|
579
|
+
region_key = "gce_datacenter"
|
|
580
|
+
case ["azure", *_]:
|
|
581
|
+
region_key = "azure_region_name"
|
|
582
|
+
case _:
|
|
583
|
+
raise PlannerServiceException(f"Unknown backend encountered: {backend}", backend)
|
|
584
|
+
|
|
585
|
+
job_params = None
|
|
586
|
+
for param_set in params:
|
|
587
|
+
if param_set["test"] == "longevity" and backend == param_set["backend"]:
|
|
588
|
+
job_params = dict(param_set)
|
|
589
|
+
job_params.pop("type", None)
|
|
590
|
+
region = job_params.pop("region", None)
|
|
591
|
+
job_params[region_key] = region
|
|
592
|
+
break
|
|
593
|
+
if not job_params:
|
|
594
|
+
raise PlannerServiceException(
|
|
595
|
+
f"Parameters not found for job {test.build_system_id}", test.build_system_id)
|
|
596
|
+
final_params = {**job_params, **common_params, **job_params}
|
|
597
|
+
queue_item = service.build_job(test.build_system_id, final_params, g.user.username)
|
|
598
|
+
info = service.get_queue_info(queue_item)
|
|
599
|
+
url = info.get("url", info.get("taskUrl", ""))
|
|
600
|
+
successes.append(url)
|
|
601
|
+
except Exception:
|
|
602
|
+
LOGGER.error("Failed to trigger %s", test.build_system_id, exc_info=True)
|
|
603
|
+
failures.append(test.build_system_id)
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
"jobs": successes,
|
|
607
|
+
"failed_to_execute": failures,
|
|
608
|
+
}
|