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.
Files changed (118) hide show
  1. argus/_version.py +21 -0
  2. argus/backend/.gitkeep +0 -0
  3. argus/backend/__init__.py +0 -0
  4. argus/backend/cli.py +57 -0
  5. argus/backend/controller/__init__.py +0 -0
  6. argus/backend/controller/admin.py +20 -0
  7. argus/backend/controller/admin_api.py +355 -0
  8. argus/backend/controller/api.py +589 -0
  9. argus/backend/controller/auth.py +67 -0
  10. argus/backend/controller/client_api.py +109 -0
  11. argus/backend/controller/main.py +316 -0
  12. argus/backend/controller/notification_api.py +72 -0
  13. argus/backend/controller/notifications.py +13 -0
  14. argus/backend/controller/planner_api.py +194 -0
  15. argus/backend/controller/team.py +129 -0
  16. argus/backend/controller/team_ui.py +19 -0
  17. argus/backend/controller/testrun_api.py +513 -0
  18. argus/backend/controller/view_api.py +188 -0
  19. argus/backend/controller/views_widgets/__init__.py +0 -0
  20. argus/backend/controller/views_widgets/graphed_stats.py +54 -0
  21. argus/backend/controller/views_widgets/graphs.py +68 -0
  22. argus/backend/controller/views_widgets/highlights.py +135 -0
  23. argus/backend/controller/views_widgets/nemesis_stats.py +26 -0
  24. argus/backend/controller/views_widgets/summary.py +43 -0
  25. argus/backend/db.py +98 -0
  26. argus/backend/error_handlers.py +41 -0
  27. argus/backend/events/event_processors.py +34 -0
  28. argus/backend/models/__init__.py +0 -0
  29. argus/backend/models/argus_ai.py +24 -0
  30. argus/backend/models/github_issue.py +60 -0
  31. argus/backend/models/plan.py +24 -0
  32. argus/backend/models/result.py +187 -0
  33. argus/backend/models/runtime_store.py +58 -0
  34. argus/backend/models/view_widgets.py +25 -0
  35. argus/backend/models/web.py +403 -0
  36. argus/backend/plugins/__init__.py +0 -0
  37. argus/backend/plugins/core.py +248 -0
  38. argus/backend/plugins/driver_matrix_tests/controller.py +66 -0
  39. argus/backend/plugins/driver_matrix_tests/model.py +429 -0
  40. argus/backend/plugins/driver_matrix_tests/plugin.py +21 -0
  41. argus/backend/plugins/driver_matrix_tests/raw_types.py +62 -0
  42. argus/backend/plugins/driver_matrix_tests/service.py +61 -0
  43. argus/backend/plugins/driver_matrix_tests/udt.py +42 -0
  44. argus/backend/plugins/generic/model.py +86 -0
  45. argus/backend/plugins/generic/plugin.py +15 -0
  46. argus/backend/plugins/generic/types.py +14 -0
  47. argus/backend/plugins/loader.py +39 -0
  48. argus/backend/plugins/sct/controller.py +224 -0
  49. argus/backend/plugins/sct/plugin.py +37 -0
  50. argus/backend/plugins/sct/resource_setup.py +177 -0
  51. argus/backend/plugins/sct/service.py +682 -0
  52. argus/backend/plugins/sct/testrun.py +288 -0
  53. argus/backend/plugins/sct/udt.py +100 -0
  54. argus/backend/plugins/sirenada/model.py +118 -0
  55. argus/backend/plugins/sirenada/plugin.py +16 -0
  56. argus/backend/service/admin.py +26 -0
  57. argus/backend/service/argus_service.py +696 -0
  58. argus/backend/service/build_system_monitor.py +185 -0
  59. argus/backend/service/client_service.py +127 -0
  60. argus/backend/service/event_service.py +18 -0
  61. argus/backend/service/github_service.py +233 -0
  62. argus/backend/service/jenkins_service.py +269 -0
  63. argus/backend/service/notification_manager.py +159 -0
  64. argus/backend/service/planner_service.py +608 -0
  65. argus/backend/service/release_manager.py +229 -0
  66. argus/backend/service/results_service.py +690 -0
  67. argus/backend/service/stats.py +610 -0
  68. argus/backend/service/team_manager_service.py +82 -0
  69. argus/backend/service/test_lookup.py +172 -0
  70. argus/backend/service/testrun.py +489 -0
  71. argus/backend/service/user.py +308 -0
  72. argus/backend/service/views.py +219 -0
  73. argus/backend/service/views_widgets/__init__.py +0 -0
  74. argus/backend/service/views_widgets/graphed_stats.py +180 -0
  75. argus/backend/service/views_widgets/highlights.py +374 -0
  76. argus/backend/service/views_widgets/nemesis_stats.py +34 -0
  77. argus/backend/template_filters.py +27 -0
  78. argus/backend/tests/__init__.py +0 -0
  79. argus/backend/tests/client_service/__init__.py +0 -0
  80. argus/backend/tests/client_service/test_submit_results.py +79 -0
  81. argus/backend/tests/conftest.py +180 -0
  82. argus/backend/tests/results_service/__init__.py +0 -0
  83. argus/backend/tests/results_service/test_best_results.py +178 -0
  84. argus/backend/tests/results_service/test_cell.py +65 -0
  85. argus/backend/tests/results_service/test_chartjs_additional_functions.py +259 -0
  86. argus/backend/tests/results_service/test_create_chartjs.py +220 -0
  87. argus/backend/tests/results_service/test_result_metadata.py +100 -0
  88. argus/backend/tests/results_service/test_results_service.py +203 -0
  89. argus/backend/tests/results_service/test_validation_rules.py +213 -0
  90. argus/backend/tests/view_widgets/__init__.py +0 -0
  91. argus/backend/tests/view_widgets/test_highlights_api.py +532 -0
  92. argus/backend/util/common.py +65 -0
  93. argus/backend/util/config.py +38 -0
  94. argus/backend/util/encoders.py +56 -0
  95. argus/backend/util/logsetup.py +80 -0
  96. argus/backend/util/module_loaders.py +30 -0
  97. argus/backend/util/send_email.py +91 -0
  98. argus/client/base.py +1 -3
  99. argus/client/driver_matrix_tests/cli.py +17 -8
  100. argus/client/generic/cli.py +4 -2
  101. argus/client/generic/client.py +1 -0
  102. argus/client/generic_result.py +48 -9
  103. argus/client/sct/client.py +1 -3
  104. argus/client/sirenada/client.py +4 -1
  105. argus/client/tests/__init__.py +0 -0
  106. argus/client/tests/conftest.py +19 -0
  107. argus/client/tests/test_package.py +45 -0
  108. argus/client/tests/test_results.py +224 -0
  109. argus/common/sct_types.py +3 -0
  110. argus/common/sirenada_types.py +1 -1
  111. {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info}/METADATA +43 -19
  112. argus_alm-0.15.2.dist-info/RECORD +122 -0
  113. {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info}/WHEEL +2 -1
  114. argus_alm-0.15.2.dist-info/entry_points.txt +3 -0
  115. argus_alm-0.15.2.dist-info/top_level.txt +1 -0
  116. argus_alm-0.14.2.dist-info/RECORD +0 -20
  117. argus_alm-0.14.2.dist-info/entry_points.txt +0 -4
  118. {argus_alm-0.14.2.dist-info → argus_alm-0.15.2.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,288 @@
1
+ from enum import Enum
2
+ import logging
3
+ from datetime import datetime, timezone
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional
6
+ from uuid import UUID
7
+
8
+ from cassandra.cqlengine import columns
9
+ from cassandra.cqlengine.models import _DoesNotExist, Model
10
+ from argus.backend.db import ScyllaCluster
11
+ from argus.backend.models.web import ArgusRelease
12
+ from argus.backend.plugins.core import PluginModelBase
13
+ from argus.backend.plugins.sct.resource_setup import ResourceSetup
14
+ from argus.backend.plugins.sct.udt import (
15
+ CloudInstanceDetails,
16
+ CloudResource,
17
+ CloudSetupDetails,
18
+ EventsBySeverity,
19
+ NemesisRunInfo,
20
+ PackageVersion,
21
+ PerformanceHDRHistogram
22
+ )
23
+
24
+ LOGGER = logging.getLogger(__name__)
25
+ SCT_REGION_PROPERTY_MAP = {
26
+ "aws": "region_name",
27
+ "aws-siren": "region_name",
28
+ "k8s-eks": "region_name",
29
+ "gce": "gce_datacenter",
30
+ "gce-siren": "gce_datacenter",
31
+ "k8s-gke": "gce_datacenter",
32
+ "azure": "azure_region_name",
33
+ "default": "region_name",
34
+ }
35
+
36
+
37
+ class SubtestType(str, Enum):
38
+ GEMINI = "gemini"
39
+ PERFORMANCE = "performance"
40
+
41
+
42
+ @dataclass(init=True, repr=True, frozen=True)
43
+ class SCTTestRunSubmissionRequest():
44
+ schema_version: str
45
+ run_id: str
46
+ job_name: str
47
+ job_url: str
48
+ started_by: str
49
+ commit_id: str
50
+ sct_config: dict | None
51
+ origin_url: Optional[str] = field(default=None)
52
+ branch_name: Optional[str] = field(default=None)
53
+ runner_public_ip: Optional[str] = field(default=None)
54
+ runner_private_ip: Optional[str] = field(default=None)
55
+
56
+
57
+ class SCTTestRun(PluginModelBase):
58
+ __table_name__ = "sct_test_run"
59
+ _plugin_name = "scylla-cluster-tests"
60
+
61
+ # Test Details
62
+ test_name = columns.Text()
63
+ stress_duration = columns.Float()
64
+ scm_revision_id = columns.Text()
65
+ branch_name = columns.Text()
66
+ origin_url = columns.Text()
67
+ started_by = columns.Text()
68
+ config_files = columns.List(value_type=columns.Text())
69
+ packages = columns.List(value_type=columns.UserDefinedType(user_type=PackageVersion))
70
+ scylla_version = columns.Text()
71
+ version_source = columns.Text()
72
+ yaml_test_duration = columns.Integer()
73
+
74
+ # Test Preset Resources
75
+ sct_runner_host = columns.UserDefinedType(user_type=CloudInstanceDetails)
76
+ region_name = columns.List(value_type=columns.Text())
77
+ cloud_setup = columns.UserDefinedType(user_type=CloudSetupDetails)
78
+
79
+ # Test Runtime Resources
80
+ allocated_resources = columns.List(value_type=columns.UserDefinedType(user_type=CloudResource))
81
+
82
+ # Test Results
83
+ events = columns.List(value_type=columns.UserDefinedType(user_type=EventsBySeverity))
84
+ nemesis_data = columns.List(value_type=columns.UserDefinedType(user_type=NemesisRunInfo))
85
+ screenshots = columns.List(value_type=columns.Text())
86
+
87
+ # Subtest
88
+ subtest_name = columns.Text()
89
+
90
+ # Gemini-related fields
91
+ oracle_nodes_count = columns.Integer()
92
+ oracle_node_ami_id = columns.Text()
93
+ oracle_node_instance_type = columns.Text()
94
+ oracle_node_scylla_version = columns.Text()
95
+ gemini_command = columns.Text()
96
+ gemini_version = columns.Text()
97
+ gemini_status = columns.Text()
98
+ gemini_seed = columns.Text()
99
+ gemini_write_ops = columns.Integer()
100
+ gemini_write_errors = columns.Integer()
101
+ gemini_read_ops = columns.Integer()
102
+ gemini_read_errors = columns.Integer()
103
+
104
+ # Performance fields
105
+ perf_op_rate_average = columns.Double()
106
+ perf_op_rate_total = columns.Double()
107
+ perf_avg_latency_99th = columns.Double()
108
+ perf_avg_latency_mean = columns.Double()
109
+ perf_total_errors = columns.Double()
110
+ stress_cmd = columns.Text()
111
+
112
+ histograms = columns.List(value_type=columns.Map(key_type=columns.Text(
113
+ ), value_type=columns.UserDefinedType(user_type=PerformanceHDRHistogram)))
114
+ test_method = columns.Ascii()
115
+
116
+ @classmethod
117
+ def _stats_query(cls) -> str:
118
+ return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
119
+ f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE build_id IN ? PER PARTITION LIMIT 15")
120
+
121
+ @classmethod
122
+ def load_test_run(cls, run_id: UUID) -> 'SCTTestRun':
123
+ return cls.get(id=run_id)
124
+
125
+ @classmethod
126
+ def submit_run(cls, request_data: dict) -> 'SCTTestRun':
127
+ req = SCTTestRunSubmissionRequest(**request_data)
128
+ return cls.from_sct_config(req=req)
129
+
130
+ @classmethod
131
+ def get_distinct_product_versions(cls, release: ArgusRelease) -> list[str]:
132
+ cluster = ScyllaCluster.get()
133
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
134
+ rows = cluster.session.execute(query=statement, parameters=(release.id,))
135
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
136
+
137
+ return sorted(list(unique_versions), reverse=True)
138
+
139
+ @classmethod
140
+ def get_version_data_for_release(cls, release_name: str):
141
+ cluster = ScyllaCluster.get()
142
+ release = ArgusRelease.get(name=release_name)
143
+ query = cluster.prepare(f"SELECT scylla_version, packages, status FROM {cls.table_name()} WHERE release_id = ?")
144
+ rows = cluster.session.execute(query=query, parameters=(release.id,))
145
+
146
+ return list(rows)
147
+
148
+ @classmethod
149
+ def get_perf_results_for_test_name(cls, build_id: str, start_time: float, test_name: str):
150
+ cluster = ScyllaCluster.get()
151
+ query = cluster.prepare(f"SELECT build_id, packages, scylla_version, test_name, perf_op_rate_average, perf_op_rate_total, "
152
+ "perf_avg_latency_99th, perf_avg_latency_mean, perf_total_errors, id, start_time, build_job_url"
153
+ f" FROM {cls.table_name()} WHERE build_id = ? AND start_time < ? AND test_name = ? ALLOW FILTERING")
154
+ rows = cluster.session.execute(query=query, parameters=(build_id, start_time, test_name))
155
+
156
+ return list(rows)
157
+
158
+ @classmethod
159
+ def init_sct_run(cls, req: SCTTestRunSubmissionRequest):
160
+ run = cls()
161
+ run.build_id = req.job_name
162
+ run.assign_categories()
163
+ try:
164
+ run.assignee = run.get_scheduled_assignee()
165
+ except _DoesNotExist:
166
+ run.assignee = None
167
+ run.start_time = datetime.utcnow()
168
+ run.id = UUID(req.run_id)
169
+ run.scm_revision_id = req.commit_id
170
+ if req.origin_url:
171
+ run.origin_url = req.origin_url
172
+ run.branch_name = req.branch_name
173
+ run.started_by = req.started_by
174
+ run.build_job_url = req.job_url
175
+
176
+ return run
177
+
178
+ @classmethod
179
+ def from_sct_config(cls, req: SCTTestRunSubmissionRequest):
180
+ try:
181
+ run = cls.get(id=req.run_id)
182
+ except cls.DoesNotExist:
183
+ run = cls.init_sct_run(req)
184
+ run.save()
185
+
186
+ if req.sct_config:
187
+ backend = req.sct_config.get("cluster_backend")
188
+ if duration_override := req.sct_config.get("stress_duration"):
189
+ run.stress_duration = float(duration_override)
190
+ region_key = SCT_REGION_PROPERTY_MAP.get(backend, SCT_REGION_PROPERTY_MAP["default"])
191
+ raw_regions = req.sct_config.get(region_key) or "undefined_region"
192
+ regions = raw_regions.split() if isinstance(raw_regions, str) else raw_regions
193
+ primary_region = regions[0]
194
+ if req.runner_public_ip: # NOTE: Legacy support, not needed otherwise
195
+ run.sct_runner_host = CloudInstanceDetails(
196
+ public_ip=req.runner_public_ip,
197
+ private_ip=req.runner_private_ip,
198
+ provider=backend,
199
+ region=primary_region,
200
+ )
201
+ run.cloud_setup = ResourceSetup.get_resource_setup(backend=backend, sct_config=req.sct_config)
202
+
203
+ run.config_files = req.sct_config.get("config_files")
204
+ run.region_name = regions
205
+ run.test_method = req.sct_config.get("test_method")
206
+ run.save()
207
+
208
+ return run
209
+
210
+ def get_resources(self) -> list[CloudResource]:
211
+ return self.allocated_resources
212
+
213
+ def get_nemeses(self) -> list[NemesisRunInfo]:
214
+ return self.nemesis_data
215
+
216
+ def get_events(self) -> list[EventsBySeverity]:
217
+ return self.events
218
+
219
+ def submit_product_version(self, version: str):
220
+ if not self.version_source:
221
+ self.scylla_version = version
222
+ try:
223
+ new_assignee = self.get_assignment(version)
224
+ except Model.DoesNotExist:
225
+ new_assignee = None
226
+ if new_assignee:
227
+ self.assignee = new_assignee
228
+
229
+ def finish_run(self, payload: dict = None):
230
+ self.end_time = datetime.utcnow()
231
+
232
+ def submit_logs(self, logs: list[dict]):
233
+ for log in logs:
234
+ if any(existing[0] == log["log_name"] for existing in self.logs):
235
+ continue
236
+ self.logs.append((log["log_name"], log["log_link"]))
237
+
238
+ def add_screenshot(self, screenshot_link: str):
239
+ self.screenshots.append(screenshot_link)
240
+
241
+ def add_nemesis(self, nemesis: NemesisRunInfo):
242
+ self.nemesis_data.append(nemesis)
243
+
244
+ def _add_new_event_type(self, event: EventsBySeverity):
245
+ self.events.append(event)
246
+
247
+ def _collect_event_message(self, event: EventsBySeverity, message: str):
248
+ if len(event.last_events) >= 100:
249
+ event.last_events = event.last_events[1:]
250
+
251
+ event.event_amount += 1
252
+ event.last_events.append(message)
253
+
254
+ def add_event(self, event_severity: str, event_message: str):
255
+ try:
256
+ event = next(filter(lambda v: v.severity ==
257
+ event_severity, self.events))
258
+ except StopIteration:
259
+ event = EventsBySeverity(
260
+ severity=event_severity, event_amount=0, last_events=[])
261
+ self._add_new_event_type(event)
262
+
263
+ self._collect_event_message(event, event_message)
264
+
265
+ def sut_timestamp(self, sut_package_name) -> float:
266
+ """converts scylla-server date to timestamp and adds revision in sub-seconds precision to differentiate
267
+ scylla versions from the same day. It's not perfect, but we don't know exact version time."""
268
+
269
+ for sut_name in [f"{sut_package_name}-upgraded",
270
+ f"{sut_package_name}-upgrade-target",
271
+ sut_package_name,
272
+ f"{sut_package_name}-target"
273
+ ]:
274
+ scylla_package = next((pkg for pkg in self.packages if pkg.name == sut_name), None)
275
+ if scylla_package:
276
+ break
277
+
278
+ if scylla_package:
279
+ return (datetime.strptime(scylla_package.date, '%Y%m%d').replace(tzinfo=timezone.utc).timestamp()
280
+ + int(scylla_package.revision_id, 16) % 1000000 / 1000000)
281
+ else:
282
+ raise ValueError(f"{sut_package_name} package not found in packages - cannot determine SUT timestamp")
283
+
284
+
285
+ class SCTJunitReports(Model):
286
+ test_id = columns.UUID(primary_key=True, partition_key=True, required=True)
287
+ file_name = columns.Text(primary_key=True, required=True)
288
+ report = columns.Text(required=True)
@@ -0,0 +1,100 @@
1
+ from time import time
2
+ from cassandra.cqlengine.usertype import UserType
3
+ from cassandra.cqlengine import columns
4
+
5
+ from argus.common.enums import ResourceState
6
+
7
+
8
+ class PackageVersion(UserType):
9
+ __type_name__ = "PackageVersion_v2"
10
+ name = columns.Text()
11
+ version = columns.Text()
12
+ date = columns.Text()
13
+ revision_id = columns.Text()
14
+ build_id = columns.Text()
15
+
16
+ def __eq__(self, other):
17
+ if isinstance(other, PackageVersion):
18
+ return all(getattr(self, a) == getattr(other, a) for a in ["name", "version", "date", "revision_id", "build_id"])
19
+ return super().__eq__(other)
20
+
21
+
22
+ class CloudInstanceDetails(UserType):
23
+ __type_name__ = "CloudInstanceDetails_v3"
24
+ provider = columns.Text()
25
+ region = columns.Text()
26
+ public_ip = columns.Text()
27
+ private_ip = columns.Text()
28
+ dc_name = columns.Text()
29
+ rack_name = columns.Text()
30
+ creation_time = columns.Integer(default=lambda: int(time()))
31
+ termination_time = columns.Integer(default=lambda: 0)
32
+ termination_reason = columns.Text(default=lambda: "")
33
+ shards_amount = columns.Integer(default=lambda: 0)
34
+
35
+
36
+ class CloudNodesInfo(UserType):
37
+ __type_name__ = "CloudNodesInfo"
38
+ image_id = columns.Text()
39
+ instance_type = columns.Text()
40
+ node_amount = columns.Integer()
41
+ post_behaviour = columns.Text()
42
+
43
+
44
+ class CloudSetupDetails(UserType):
45
+ __type_name__ = "CloudSetupDetails"
46
+ db_node = columns.UserDefinedType(user_type=CloudNodesInfo)
47
+ loader_node = columns.UserDefinedType(user_type=CloudNodesInfo)
48
+ monitor_node = columns.UserDefinedType(user_type=CloudNodesInfo)
49
+ backend = columns.Text()
50
+
51
+
52
+ class CloudResource(UserType):
53
+ __type_name__ = "CloudResource_v3"
54
+ name = columns.Text()
55
+ state = columns.Text(default=lambda: ResourceState.RUNNING.value)
56
+ resource_type = columns.Text()
57
+ instance_info = columns.UserDefinedType(user_type=CloudInstanceDetails)
58
+
59
+ def get_instance_info(self) -> CloudInstanceDetails:
60
+ return self.instance_info
61
+
62
+
63
+ class EventsBySeverity(UserType):
64
+ __type_name__ = "EventsBySeverity"
65
+ severity = columns.Text()
66
+ event_amount = columns.Integer()
67
+ last_events = columns.List(value_type=columns.Text())
68
+
69
+
70
+ class NodeDescription(UserType):
71
+ __type_name__ = "NodeDescription"
72
+ name = columns.Text()
73
+ ip = columns.Text()
74
+ shards = columns.Integer()
75
+
76
+
77
+ class NemesisRunInfo(UserType):
78
+ __type_name__ = "NemesisRunInfo"
79
+
80
+ class_name = columns.Text()
81
+ name = columns.Text()
82
+ duration = columns.Integer()
83
+ target_node = columns.UserDefinedType(user_type=NodeDescription)
84
+ status = columns.Text()
85
+ start_time = columns.Integer()
86
+ end_time = columns.Integer()
87
+ stack_trace = columns.Text()
88
+
89
+
90
+ class PerformanceHDRHistogram(UserType):
91
+ start_time = columns.Integer()
92
+ percentile_90 = columns.Float()
93
+ percentile_50 = columns.Float()
94
+ percentile_99_999 = columns.Float()
95
+ percentile_95 = columns.Float()
96
+ end_time = columns.Float()
97
+ percentile_99_99 = columns.Float()
98
+ percentile_99 = columns.Float()
99
+ stddev = columns.Float()
100
+ percentile_99_9 = columns.Float()
@@ -0,0 +1,118 @@
1
+ from datetime import datetime
2
+ from uuid import UUID
3
+ from cassandra.cqlengine import columns
4
+ from cassandra.cqlengine.usertype import UserType
5
+ from cassandra.cqlengine.models import Model
6
+ from argus.backend.db import ScyllaCluster
7
+ from argus.backend.models.web import ArgusRelease
8
+ from argus.backend.plugins.core import PluginModelBase
9
+ from argus.common.sirenada_types import RawSirenadaRequest, SirenadaPluginException
10
+ from argus.common.enums import TestStatus
11
+
12
+
13
+ class SirenadaTest(UserType):
14
+ test_name = columns.Text()
15
+ class_name = columns.Text()
16
+ file_name = columns.Text()
17
+ browser_type = columns.Text()
18
+ cluster_type = columns.Text()
19
+ status = columns.Text()
20
+ duration = columns.Float()
21
+ message = columns.Text()
22
+ start_time = columns.DateTime()
23
+ stack_trace = columns.Text()
24
+ screenshot_file = columns.Text()
25
+ s3_folder_id = columns.Text()
26
+ requests_file = columns.Text()
27
+ sirenada_test_id = columns.Text()
28
+ sirenada_user = columns.Text()
29
+ sirenada_password = columns.Text()
30
+
31
+
32
+ class SirenadaRun(PluginModelBase):
33
+ _plugin_name = "sirenada"
34
+ __table_name__ = "sirenada_run"
35
+ logs = columns.Map(key_type=columns.Text(), value_type=columns.Text())
36
+ # TODO: Legacy field name, should be renamed to product_version and abstracted
37
+ scylla_version = columns.Text()
38
+ region = columns.Text()
39
+ sirenada_test_ids = columns.List(value_type=columns.Text())
40
+ s3_folder_ids = columns.List(value_type=columns.Tuple(columns.Text(), columns.Text()))
41
+ browsers = columns.List(value_type=columns.Text())
42
+ clusters = columns.List(value_type=columns.Text())
43
+ sct_test_id = columns.UUID()
44
+ results = columns.List(value_type=columns.UserDefinedType(user_type=SirenadaTest))
45
+
46
+ @classmethod
47
+ def _stats_query(cls) -> str:
48
+ return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
49
+ f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE build_id IN ? PER PARTITION LIMIT 15")
50
+
51
+ @classmethod
52
+ def get_distinct_product_versions(cls, release: ArgusRelease, cluster: ScyllaCluster = None) -> list[str]:
53
+ if not cluster:
54
+ cluster = ScyllaCluster.get()
55
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
56
+ rows = cluster.session.execute(query=statement, parameters=(release.id,))
57
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
58
+
59
+ return sorted(list(unique_versions), reverse=True)
60
+
61
+ def submit_product_version(self, version: str):
62
+ self.scylla_version = version
63
+ try:
64
+ new_assignee = self.get_assignment(version)
65
+ except Model.DoesNotExist:
66
+ new_assignee = None
67
+ if new_assignee:
68
+ self.assignee = new_assignee
69
+
70
+ def submit_logs(self, logs: dict[str, str]):
71
+ raise SirenadaPluginException("Log submission is not supported for Sirenada")
72
+
73
+ def finish_run(self, payload: dict = None):
74
+ raise SirenadaPluginException("Sirenada runs do not need finalization")
75
+
76
+ @classmethod
77
+ def load_test_run(cls, run_id: UUID) -> 'SirenadaRun':
78
+ return cls.get(id=run_id)
79
+
80
+ @classmethod
81
+ def submit_run(cls, request_data: RawSirenadaRequest) -> 'SirenadaRun':
82
+ try:
83
+ run = cls.get(id=UUID(request_data["run_id"]))
84
+ except cls.DoesNotExist:
85
+ run = cls()
86
+ run.id = request_data["run_id"] # FIXME: Validate pls
87
+ run.build_id = request_data["build_id"]
88
+ run.start_time = datetime.utcnow()
89
+ run.assign_categories()
90
+ run.build_job_url = request_data["build_job_url"]
91
+ run.region = request_data["region"]
92
+ run.status = TestStatus.PASSED.value
93
+ try:
94
+ run.assignee = run.get_scheduled_assignee()
95
+ except Model.DoesNotExist:
96
+ run.assignee = None
97
+
98
+ for raw_case in request_data["results"]:
99
+ case = SirenadaTest(**raw_case)
100
+ if case.status in ["failed", "error"] and run.status not in [TestStatus.FAILED.value, TestStatus.ABORTED.value]:
101
+ run.status = TestStatus.FAILED.value
102
+ run.results.append(case)
103
+
104
+ if case.sirenada_test_id not in run.sirenada_test_ids:
105
+ run.sirenada_test_ids.append(case.sirenada_test_id)
106
+
107
+ if case.browser_type not in run.browsers:
108
+ run.browsers.append(case.browser_type)
109
+
110
+ if case.cluster_type not in run.clusters:
111
+ run.clusters.append(case.cluster_type)
112
+
113
+ if (case.s3_folder_id, case.sirenada_test_id) not in run.s3_folder_ids and case.s3_folder_id:
114
+ run.s3_folder_ids.append((case.s3_folder_id, case.sirenada_test_id))
115
+
116
+ run.save()
117
+
118
+ return run
@@ -0,0 +1,16 @@
1
+ from flask import Blueprint
2
+
3
+ from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
+ from argus.backend.plugins.sirenada.model import SirenadaRun, SirenadaTest
5
+
6
+
7
+ class PluginInfo(PluginInfoBase):
8
+ name: str = "sirenada"
9
+ model: PluginModelBase = SirenadaRun
10
+ controller: Blueprint = None
11
+ all_models = [
12
+ SirenadaRun
13
+ ]
14
+ all_types = [
15
+ SirenadaTest
16
+ ]
@@ -0,0 +1,26 @@
1
+
2
+ from flask import g, current_app, session
3
+
4
+ from argus.backend.db import ScyllaCluster
5
+ from argus.backend.models.web import (
6
+ ArgusRelease,
7
+ ArgusGroup,
8
+ ArgusTest,
9
+ ArgusSchedule,
10
+ ArgusScheduleAssignee,
11
+ ArgusScheduleGroup,
12
+ ArgusScheduleTest,
13
+ ArgusTestRunComment,
14
+ ArgusEvent,
15
+ ArgusEventTypes,
16
+ ReleasePlannerComment,
17
+ User,
18
+ UserOauthToken,
19
+ WebFileStorage,
20
+ )
21
+
22
+
23
+ class AdminService:
24
+ def __init__(self, database_session=None):
25
+ self.session = database_session if database_session else ScyllaCluster.get_session()
26
+ self.database = ScyllaCluster.get()