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,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()
|