argus-alm 0.11.4__tar.gz → 0.11.6__tar.gz

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 (77) hide show
  1. {argus_alm-0.11.4 → argus_alm-0.11.6}/PKG-INFO +1 -1
  2. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/model.py +5 -1
  3. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/controller.py +30 -0
  4. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/plugin.py +3 -1
  5. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/service.py +158 -2
  6. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/testrun.py +78 -13
  7. argus_alm-0.11.6/argus/backend/plugins/sct/types.py +38 -0
  8. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/udt.py +13 -0
  9. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/build_system_monitor.py +21 -3
  10. argus_alm-0.11.6/argus/backend/service/event_service.py +18 -0
  11. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/stats.py +69 -15
  12. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/testrun.py +13 -21
  13. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/encoders.py +3 -0
  14. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/driver_matrix_tests/client.py +19 -6
  15. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/sct/client.py +31 -0
  16. {argus_alm-0.11.4 → argus_alm-0.11.6}/pyproject.toml +1 -1
  17. argus_alm-0.11.4/setup.py +0 -45
  18. {argus_alm-0.11.4 → argus_alm-0.11.6}/LICENSE +0 -0
  19. {argus_alm-0.11.4 → argus_alm-0.11.6}/README.md +0 -0
  20. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/__init__.py +0 -0
  21. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/.gitkeep +0 -0
  22. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/__init__.py +0 -0
  23. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/cli.py +0 -0
  24. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/__init__.py +0 -0
  25. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/admin.py +0 -0
  26. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/admin_api.py +0 -0
  27. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/api.py +0 -0
  28. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/auth.py +0 -0
  29. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/client_api.py +0 -0
  30. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/main.py +0 -0
  31. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/notification_api.py +0 -0
  32. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/notifications.py +0 -0
  33. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/team.py +0 -0
  34. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/team_ui.py +0 -0
  35. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/controller/testrun_api.py +0 -0
  36. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/db.py +0 -0
  37. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/error_handlers.py +0 -0
  38. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/events/event_processors.py +0 -0
  39. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/models/__init__.py +0 -0
  40. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/models/web.py +0 -0
  41. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/__init__.py +0 -0
  42. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/core.py +0 -0
  43. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
  44. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/plugin.py +0 -0
  45. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
  46. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
  47. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/loader.py +0 -0
  48. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sct/resource_setup.py +0 -0
  49. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/model.py +0 -0
  50. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/plugin.py +0 -0
  51. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/types.py +0 -0
  52. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/admin.py +0 -0
  53. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/argus_service.py +0 -0
  54. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/client_service.py +0 -0
  55. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/notification_manager.py +0 -0
  56. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/release_manager.py +0 -0
  57. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/team_manager_service.py +0 -0
  58. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/service/user.py +0 -0
  59. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/template_filters.py +0 -0
  60. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/common.py +0 -0
  61. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/config.py +0 -0
  62. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/enums.py +0 -0
  63. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/logsetup.py +0 -0
  64. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/module_loaders.py +0 -0
  65. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/backend/util/send_email.py +0 -0
  66. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/__init__.py +0 -0
  67. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/base.py +0 -0
  68. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/sct/types.py +0 -0
  69. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/client/sirenada/client.py +0 -0
  70. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/.gitkeep +0 -0
  71. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/argus_json.py +0 -0
  72. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/cloud_types.py +0 -0
  73. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/config.py +0 -0
  74. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/db_types.py +0 -0
  75. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/interface.py +0 -0
  76. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/testrun.py +0 -0
  77. {argus_alm-0.11.4 → argus_alm-0.11.6}/argus/db/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: argus-alm
3
- Version: 0.11.4
3
+ Version: 0.11.6
4
4
  Summary: Argus
5
5
  Home-page: https://github.com/scylladb/argus
6
6
  License: Apache-2.0
@@ -58,6 +58,10 @@ class DriverTestRun(PluginModelBase):
58
58
  run.build_id = req.job_name
59
59
  run.build_job_url = req.job_url
60
60
  run.assign_categories()
61
+ try:
62
+ run.assignee = run.get_scheduled_assignee()
63
+ except Exception: # pylint: disable=broad-except
64
+ run.assignee = None
61
65
  for key, value in req.test_environment.items():
62
66
  env_info = EnvironmentInfo()
63
67
  env_info.key = key
@@ -102,7 +106,7 @@ class DriverTestRun(PluginModelBase):
102
106
  collection.suites.append(suite)
103
107
  run.test_collection.append(collection)
104
108
 
105
- run.status = run._determine_run_status()
109
+ run.status = run._determine_run_status().value
106
110
  run.save()
107
111
  return run
108
112
 
@@ -114,6 +114,36 @@ def sct_events_submit(run_id: str):
114
114
  }
115
115
 
116
116
 
117
+ @bp.route("/<string:run_id>/gemini/submit", methods=["POST"])
118
+ @api_login_required
119
+ def sct_gemini_results_submit(run_id: str):
120
+ payload = get_payload(request)
121
+ result = SCTService.submit_gemini_results(run_id=run_id, gemini_data=payload["gemini_data"])
122
+ return {
123
+ "status": "ok",
124
+ "response": result
125
+ }
126
+
127
+ @bp.route("/<string:run_id>/performance/submit", methods=["POST"])
128
+ @api_login_required
129
+ def sct_performance_results_submit(run_id: str):
130
+ payload = get_payload(request)
131
+ result = SCTService.submit_performance_results(run_id=run_id, performance_results=payload["performance_results"])
132
+ return {
133
+ "status": "ok",
134
+ "response": result
135
+ }
136
+
137
+ @bp.route("/<string:run_id>/performance/history", methods=["GET"])
138
+ @api_login_required
139
+ def sct_get_performance_history(run_id: str):
140
+ result = SCTService.get_performance_history_for_test(run_id=run_id)
141
+ return {
142
+ "status": "ok",
143
+ "response": result
144
+ }
145
+
146
+
117
147
  @bp.route("/release/<string:release_name>/kernels", methods=["GET"])
118
148
  @api_login_required
119
149
  def sct_get_kernel_report(release_name: str):
@@ -12,6 +12,7 @@ from argus.backend.plugins.sct.udt import (
12
12
  NemesisRunInfo,
13
13
  NodeDescription,
14
14
  PackageVersion,
15
+ PerformanceHDRHistogram,
15
16
  )
16
17
 
17
18
 
@@ -31,5 +32,6 @@ class PluginInfo(PluginInfoBase):
31
32
  CloudSetupDetails,
32
33
  CloudNodesInfo,
33
34
  CloudInstanceDetails,
34
- PackageVersion
35
+ PackageVersion,
36
+ PerformanceHDRHistogram,
35
37
  ]
@@ -1,7 +1,12 @@
1
1
  from dataclasses import dataclass
2
+ from functools import reduce
2
3
  import logging
4
+ import math
3
5
  from time import time
4
- from argus.backend.plugins.sct.testrun import SCTTestRun
6
+ from flask import g
7
+ from argus.backend.models.web import ArgusEventTypes
8
+ from argus.backend.plugins.sct.testrun import SCTTestRun, SubtestType
9
+ from argus.backend.plugins.sct.types import GeminiResultsRequest, PerformanceResultsRequest
5
10
  from argus.backend.plugins.sct.udt import (
6
11
  CloudInstanceDetails,
7
12
  CloudResource,
@@ -9,8 +14,11 @@ from argus.backend.plugins.sct.udt import (
9
14
  NemesisRunInfo,
10
15
  NodeDescription,
11
16
  PackageVersion,
17
+ PerformanceHDRHistogram,
12
18
  )
13
- from argus.backend.util.enums import NemesisStatus, ResourceState
19
+ from argus.backend.service.event_service import EventService
20
+ from argus.backend.util.common import get_build_number
21
+ from argus.backend.util.enums import NemesisStatus, ResourceState, TestStatus
14
22
 
15
23
  LOGGER = logging.getLogger(__name__)
16
24
 
@@ -91,6 +99,154 @@ class SCTService:
91
99
 
92
100
  return "submitted"
93
101
 
102
+ @staticmethod
103
+ def submit_gemini_results(run_id: str, gemini_data: GeminiResultsRequest) -> str:
104
+ try:
105
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
106
+ run.subtest_name = SubtestType.GEMINI.value
107
+ run.oracle_nodes_count = gemini_data.get("oracle_nodes_count")
108
+ run.oracle_node_ami_id = gemini_data.get("oracle_node_ami_id")
109
+ run.oracle_node_instance_type = gemini_data.get("oracle_node_instance_type")
110
+ run.oracle_node_scylla_version = gemini_data.get("oracle_node_scylla_version")
111
+ run.gemini_command = gemini_data.get("gemini_command")
112
+ run.gemini_version = gemini_data.get("gemini_version")
113
+ run.gemini_status = gemini_data.get("gemini_status")
114
+ run.gemini_seed = str(gemini_data.get("gemini_seed"))
115
+ run.gemini_write_ops = gemini_data.get("gemini_write_ops")
116
+ run.gemini_write_errors = gemini_data.get("gemini_write_errors")
117
+ run.gemini_read_ops = gemini_data.get("gemini_read_ops")
118
+ run.gemini_read_errors = gemini_data.get("gemini_read_errors")
119
+ run.save()
120
+
121
+ if run.gemini_status != "PASSED":
122
+ run.status = TestStatus.FAILED
123
+ EventService.create_run_event(kind=ArgusEventTypes.TestRunStatusChanged, body={
124
+ "message": "[{username}] Setting run status to {status} due to Gemini reporting following status: {gemini_status}",
125
+ "username": g.user.username,
126
+ "status": TestStatus.FAILED.value,
127
+ "gemini_status": run.gemini_status,
128
+ }, user_id=g.user.id, run_id=run_id, release_id=run.release_id, test_id=run.test_id)
129
+ run.save()
130
+ except SCTTestRun.DoesNotExist as exception:
131
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
132
+ raise SCTServiceException("Run not found", run_id) from exception
133
+
134
+ return "submitted"
135
+
136
+ @staticmethod
137
+ def submit_performance_results(run_id: str, performance_results: PerformanceResultsRequest):
138
+ # pylint: disable=too-many-statements
139
+ try:
140
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
141
+ run.subtest_name = SubtestType.PERFORMANCE.value
142
+ run.perf_op_rate_average = performance_results.get("perf_op_rate_average")
143
+ run.perf_op_rate_total = performance_results.get("perf_op_rate_total")
144
+ run.perf_avg_latency_99th = performance_results.get("perf_avg_latency_99th")
145
+ run.perf_avg_latency_mean = performance_results.get("perf_avg_latency_mean")
146
+ run.perf_total_errors = performance_results.get("perf_total_errors")
147
+ run.stress_cmd = performance_results.get("stress_cmd")
148
+ run.test_name = performance_results.get("test_name")
149
+ run.save()
150
+
151
+ is_latency_test = "latency" in run.test_name
152
+ threshold_negative = -10
153
+
154
+ def cmp(lhs, rhs):
155
+ delta = rhs - lhs
156
+ change = int(math.fabs(delta) * 100 / rhs)
157
+ return change if delta >= 0 else change * -1
158
+
159
+ previous_runs = SCTTestRun.get_perf_results_for_test_name(run.build_id, run.start_time, run.test_name)
160
+ metrics_to_check = ["perf_avg_latency_99th", "perf_avg_latency_mean"] if is_latency_test else ["perf_op_rate_total"]
161
+
162
+ older_runs_by_version = {}
163
+ for prev_run in previous_runs:
164
+ if not older_runs_by_version.get(prev_run["scylla_version"]):
165
+ older_runs_by_version[prev_run["scylla_version"]] = []
166
+ older_runs_by_version[prev_run["scylla_version"]].append(prev_run)
167
+
168
+ regression_found = False
169
+ regression_info = {
170
+ "version": None,
171
+ "delta": None,
172
+ "id": None,
173
+ "metric": None,
174
+ "job_url": None,
175
+ }
176
+
177
+ if performance_results["histograms"]:
178
+ for histogram in performance_results["histograms"]:
179
+ run.histograms = { k: PerformanceHDRHistogram(**v) for k, v in histogram.items() }
180
+
181
+ for version, runs in older_runs_by_version.items():
182
+ for metric in metrics_to_check:
183
+ # pylint: disable=cell-var-from-loop
184
+ best_run = sorted(runs, reverse=(not is_latency_test), key=lambda v: v[metric])[0]
185
+ last_run = runs[0]
186
+
187
+ metric_to_best = cmp(run[metric], best_run[metric])
188
+ metric_to_last = cmp(run[metric], last_run[metric])
189
+ if metric_to_last < threshold_negative:
190
+ regression_found = True
191
+ regression_info["metric"] = metric
192
+ regression_info["version"] = version
193
+ regression_info["job_url"] = last_run["build_job_url"]
194
+ regression_info["id"] = str(last_run["id"])
195
+ regression_info["delta"] = metric_to_last
196
+ break
197
+
198
+ if metric_to_best < threshold_negative:
199
+ regression_found = True
200
+ regression_info["metric"] = metric
201
+ regression_info["version"] = version
202
+ regression_info["job_url"] = best_run["build_job_url"]
203
+ regression_info["id"] = str(best_run["id"])
204
+ regression_info["delta"] = metric_to_best
205
+ break
206
+
207
+ if regression_found:
208
+ break
209
+
210
+ if regression_found:
211
+ run.status = TestStatus.FAILED.value
212
+ run.save()
213
+ EventService.create_run_event(kind=ArgusEventTypes.TestRunStatusChanged, body={
214
+ "message": "[{username}] Setting run status to {status} due to performance metric '{metric}' falling "
215
+ "below allowed threshold ({threshold_negative}): {delta}% compared to "
216
+ "<a href='/test/{test_id}/runs?additionalRuns[]={base_run_id}&additionalRuns[]={previous_run_id}'>This {version} (#{build_number}) run</a>",
217
+ "username": g.user.username,
218
+ "status": TestStatus.FAILED.value,
219
+ "metric": regression_info["metric"],
220
+ "threshold_negative": threshold_negative,
221
+ "delta": regression_info["delta"],
222
+ "test_id": str(run.test_id),
223
+ "base_run_id": str(run.id),
224
+ "previous_run_id": regression_info["id"],
225
+ "version": regression_info["version"],
226
+ "build_number": get_build_number(regression_info["job_url"])
227
+ }, user_id=g.user.id, run_id=run_id, release_id=run.release_id, test_id=run.test_id)
228
+ else:
229
+ # NOTE: This will override status set by SCT Events.
230
+ run.status = TestStatus.PASSED.value
231
+ run.save()
232
+
233
+ except SCTTestRun.DoesNotExist as exception:
234
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
235
+ raise SCTServiceException("Run not found", run_id) from exception
236
+
237
+ return "submitted"
238
+
239
+ @staticmethod
240
+ def get_performance_history_for_test(run_id: str):
241
+ try:
242
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
243
+ rows = run.get_perf_results_for_test_name(build_id=run.build_id, start_time=run.start_time, test_name=run.test_name)
244
+ return rows
245
+ except SCTTestRun.DoesNotExist as exception:
246
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
247
+ raise SCTServiceException("Run not found", run_id) from exception
248
+
249
+
94
250
  @staticmethod
95
251
  def create_resource(run_id: str, resource_details: dict) -> str:
96
252
  instance_details = CloudInstanceDetails(**resource_details.pop("instance_details"))
@@ -1,6 +1,8 @@
1
+ from enum import Enum
1
2
  import logging
2
3
  from datetime import datetime
3
- from dataclasses import dataclass
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional
4
6
  from uuid import UUID
5
7
 
6
8
  from cassandra.cqlengine import columns
@@ -16,6 +18,7 @@ from argus.backend.plugins.sct.udt import (
16
18
  EventsBySeverity,
17
19
  NemesisRunInfo,
18
20
  PackageVersion,
21
+ PerformanceHDRHistogram
19
22
  )
20
23
 
21
24
  LOGGER = logging.getLogger(__name__)
@@ -31,6 +34,11 @@ SCT_REGION_PROPERTY_MAP = {
31
34
  }
32
35
 
33
36
 
37
+ class SubtestType(str, Enum):
38
+ GEMINI = "gemini"
39
+ PERFORMANCE = "performance"
40
+
41
+
34
42
  @dataclass(init=True, repr=True, frozen=True)
35
43
  class SCTTestRunSubmissionRequest():
36
44
  schema_version: str
@@ -39,9 +47,9 @@ class SCTTestRunSubmissionRequest():
39
47
  job_url: str
40
48
  started_by: str
41
49
  commit_id: str
42
- runner_public_ip: str
43
- runner_private_ip: str
44
- sct_config: dict
50
+ sct_config: dict | None
51
+ runner_public_ip: Optional[str] = field(default=None)
52
+ runner_private_ip: Optional[str] = field(default=None)
45
53
 
46
54
 
47
55
  class SCTTestRun(PluginModelBase):
@@ -49,6 +57,7 @@ class SCTTestRun(PluginModelBase):
49
57
  _plugin_name = "scylla-cluster-tests"
50
58
 
51
59
  # Test Details
60
+ test_name = columns.Text()
52
61
  scm_revision_id = columns.Text()
53
62
  started_by = columns.Text()
54
63
  config_files = columns.List(value_type=columns.Text())
@@ -69,6 +78,33 @@ class SCTTestRun(PluginModelBase):
69
78
  nemesis_data = columns.List(value_type=columns.UserDefinedType(user_type=NemesisRunInfo))
70
79
  screenshots = columns.List(value_type=columns.Text())
71
80
 
81
+ # Subtest
82
+ subtest_name = columns.Text()
83
+
84
+ # Gemini-related fields
85
+ oracle_nodes_count = columns.Integer()
86
+ oracle_node_ami_id = columns.Text()
87
+ oracle_node_instance_type = columns.Text()
88
+ oracle_node_scylla_version = columns.Text()
89
+ gemini_command = columns.Text()
90
+ gemini_version = columns.Text()
91
+ gemini_status = columns.Text()
92
+ gemini_seed = columns.Text()
93
+ gemini_write_ops = columns.Integer()
94
+ gemini_write_errors = columns.Integer()
95
+ gemini_read_ops = columns.Integer()
96
+ gemini_read_errors = columns.Integer()
97
+
98
+ # Performance fields
99
+ perf_op_rate_average = columns.Double()
100
+ perf_op_rate_total = columns.Double()
101
+ perf_avg_latency_99th = columns.Double()
102
+ perf_avg_latency_mean = columns.Double()
103
+ perf_total_errors = columns.Double()
104
+ stress_cmd = columns.Text()
105
+
106
+ histograms = columns.List(value_type=columns.Map(key_type=columns.Text(), value_type=columns.UserDefinedType(user_type=PerformanceHDRHistogram)))
107
+
72
108
  @classmethod
73
109
  def _stats_query(cls) -> str:
74
110
  return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
@@ -102,7 +138,17 @@ class SCTTestRun(PluginModelBase):
102
138
  return list(rows)
103
139
 
104
140
  @classmethod
105
- def from_sct_config(cls, req: SCTTestRunSubmissionRequest):
141
+ def get_perf_results_for_test_name(cls, build_id: str, start_time: float, test_name: str):
142
+ cluster = ScyllaCluster.get()
143
+ query = cluster.prepare(f"SELECT build_id, packages, scylla_version, test_name, perf_op_rate_average, perf_op_rate_total, "
144
+ "perf_avg_latency_99th, perf_avg_latency_mean, perf_total_errors, id, start_time, build_job_url"
145
+ f" FROM {cls.table_name()} WHERE build_id = ? AND start_time < ? AND test_name = ? ALLOW FILTERING")
146
+ rows = cluster.session.execute(query=query, parameters=(build_id, start_time, test_name))
147
+
148
+ return list(rows)
149
+
150
+ @classmethod
151
+ def init_sct_run(cls, req: SCTTestRunSubmissionRequest):
106
152
  run = cls()
107
153
  run.build_id = req.job_name
108
154
  run.assign_categories()
@@ -116,16 +162,35 @@ class SCTTestRun(PluginModelBase):
116
162
  run.started_by = req.started_by
117
163
  run.build_job_url = req.job_url
118
164
 
119
- backend = req.sct_config.get("cluster_backend")
120
- region_key = SCT_REGION_PROPERTY_MAP.get(backend, SCT_REGION_PROPERTY_MAP["default"])
121
- raw_regions = req.sct_config.get(region_key) or "undefined_region"
122
- regions = raw_regions.split() if isinstance(raw_regions, str) else raw_regions
165
+ return run
123
166
 
124
- run.cloud_setup = ResourceSetup.get_resource_setup(backend=backend, sct_config=req.sct_config)
167
+ @classmethod
168
+ def from_sct_config(cls, req: SCTTestRunSubmissionRequest):
169
+ try:
170
+ run = cls.get(id=req.run_id)
171
+ except cls.DoesNotExist:
172
+ run = cls.init_sct_run(req)
173
+ run.save()
174
+
175
+ if req.sct_config:
176
+ backend = req.sct_config.get("cluster_backend")
177
+ region_key = SCT_REGION_PROPERTY_MAP.get(backend, SCT_REGION_PROPERTY_MAP["default"])
178
+ raw_regions = req.sct_config.get(region_key) or "undefined_region"
179
+ regions = raw_regions.split() if isinstance(raw_regions, str) else raw_regions
180
+ primary_region = regions[0]
181
+ if req.runner_public_ip: # NOTE: Legacy support, not needed otherwise
182
+ run.sct_runner_host = CloudInstanceDetails(
183
+ public_ip=req.runner_public_ip,
184
+ private_ip=req.runner_private_ip,
185
+ provider=backend,
186
+ region=primary_region,
187
+ )
188
+ run.cloud_setup = ResourceSetup.get_resource_setup(backend=backend, sct_config=req.sct_config)
189
+
190
+ run.config_files = req.sct_config.get("config_files")
191
+ run.region_name = regions
192
+ run.save()
125
193
 
126
- run.config_files = req.sct_config.get("config_files")
127
- run.region_name = regions
128
- run.save()
129
194
  return run
130
195
 
131
196
  def get_resources(self) -> list[CloudResource]:
@@ -0,0 +1,38 @@
1
+ from typing import TypedDict
2
+
3
+ class RawHDRHistogram(TypedDict):
4
+ start_time: int
5
+ percentile_90: float
6
+ percentile_50: float
7
+ percentile_99_999: float
8
+ percentile_95: float
9
+ end_time: float
10
+ percentile_99_99: float
11
+ percentile_99: float
12
+ stddev: float
13
+ percentile_99_9: float
14
+
15
+ class GeminiResultsRequest(TypedDict):
16
+ oracle_nodes_count: int
17
+ oracle_node_ami_id: str
18
+ oracle_node_instance_type: str
19
+ oracle_node_scylla_version: str
20
+ gemini_command: str
21
+ gemini_version: str
22
+ gemini_status: str
23
+ gemini_seed: str
24
+ gemini_write_ops: int
25
+ gemini_write_errors: int
26
+ gemini_read_ops: int
27
+ gemini_read_errors: int
28
+
29
+ class PerformanceResultsRequest(TypedDict):
30
+ test_name: str
31
+ stress_cmd: str
32
+ perf_op_rate_average: float
33
+ perf_op_rate_total: float
34
+ perf_avg_latency_99th: float
35
+ perf_avg_latency_mean: float
36
+ perf_total_errors: str
37
+
38
+ histograms: list[dict[str, RawHDRHistogram]] | None
@@ -78,3 +78,16 @@ class NemesisRunInfo(UserType):
78
78
  start_time = columns.Integer()
79
79
  end_time = columns.Integer()
80
80
  stack_trace = columns.Text()
81
+
82
+
83
+ class PerformanceHDRHistogram(UserType):
84
+ start_time = columns.Integer()
85
+ percentile_90 = columns.Float()
86
+ percentile_50 = columns.Float()
87
+ percentile_99_999 = columns.Float()
88
+ percentile_95 = columns.Float()
89
+ end_time = columns.Float()
90
+ percentile_99_99 = columns.Float()
91
+ percentile_99 = columns.Float()
92
+ stddev = columns.Float()
93
+ percentile_99_9 = columns.Float()
@@ -78,7 +78,7 @@ class JenkinsMonitor(ArgusTestsMonitor):
78
78
  def collect(self):
79
79
  click.echo("Collecting new tests from jenkins")
80
80
  all_jobs = self._jenkins.get_all_jobs()
81
- all_monitored_folders = [job for job in all_jobs if job["name"] in self._monitored_releases]
81
+ all_monitored_folders = [job for job in all_jobs if job["fullname"] in self._monitored_releases]
82
82
  for release in all_monitored_folders:
83
83
  LOGGER.info("Processing release %s", release["name"])
84
84
  try:
@@ -89,8 +89,23 @@ class JenkinsMonitor(ArgusTestsMonitor):
89
89
  saved_release = self.create_release(release["name"])
90
90
  self._existing_releases.append(saved_release)
91
91
 
92
- groups = self.collect_groups_for_release(release["jobs"])
92
+ try:
93
+ groups = self.collect_groups_for_release(release["jobs"])
94
+ except KeyError:
95
+ LOGGER.error("Empty release!\n %s", release)
96
+ continue
93
97
  folder_stack = [dict(parent_name="", parent_display_name="", group=g) for g in reversed(groups)]
98
+ root_folder = {
99
+ "parent_name": "",
100
+ "parent_display_name": "",
101
+ "group": {
102
+ "name": f"{release['fullname']}-root",
103
+ "displayName": "-- root directory --",
104
+ "fullname": release["fullname"],
105
+ "jobs": self.collect_root_folder_jobs(release["jobs"]),
106
+ }
107
+ }
108
+ folder_stack.append(root_folder)
94
109
  while len(folder_stack) != 0:
95
110
  group_dict = folder_stack.pop()
96
111
  group = group_dict["group"]
@@ -104,7 +119,7 @@ class JenkinsMonitor(ArgusTestsMonitor):
104
119
  LOGGER.warning(
105
120
  "Group %s for release %s doesn't exist, creating...", group_name, saved_release.name)
106
121
  try:
107
- display_name = self._jenkins.get_job_info(name=group["fullname"])["displayName"]
122
+ display_name = group.get("displayName", self._jenkins.get_job_info(name=group["fullname"])["displayName"])
108
123
  display_name = display_name if not group_dict[
109
124
  "parent_display_name"] else f"{group_dict['parent_display_name']} - {display_name}"
110
125
  except Exception:
@@ -138,3 +153,6 @@ class JenkinsMonitor(ArgusTestsMonitor):
138
153
  groups = [group for group in groups if self.check_filter(group["name"])]
139
154
 
140
155
  return groups
156
+
157
+ def collect_root_folder_jobs(self, jobs):
158
+ return [job for job in jobs if "WorkflowJob" in job["_class"]]
@@ -0,0 +1,18 @@
1
+ from datetime import datetime
2
+ import json
3
+ from argus.backend.models.web import ArgusEvent, ArgusEventTypes
4
+
5
+
6
+ class EventService:
7
+ @staticmethod
8
+ def create_run_event(kind: ArgusEventTypes, body: dict, user_id=None, run_id=None, release_id=None, group_id=None, test_id=None):
9
+ event = ArgusEvent()
10
+ event.release_id = release_id
11
+ event.group_id = group_id
12
+ event.test_id = test_id
13
+ event.user_id = user_id
14
+ event.run_id = run_id
15
+ event.body = json.dumps(body, ensure_ascii=True, separators=(',', ':'))
16
+ event.kind = kind.value
17
+ event.created_at = datetime.utcnow()
18
+ event.save()
@@ -76,6 +76,70 @@ class ComparableTestStatus:
76
76
  return self._get_prio() <= __o._get_prio()
77
77
 
78
78
 
79
+ class ComparableTestInvestigationStatus:
80
+ PRIORITY_MAP = {
81
+ TestInvestigationStatus.NOT_INVESTIGATED: 10,
82
+ TestInvestigationStatus.IN_PROGRESS: 9,
83
+ TestInvestigationStatus.INVESTIGATED: 8,
84
+ TestInvestigationStatus.IGNORED: 7,
85
+ }
86
+
87
+ def __init__(self, status: TestInvestigationStatus):
88
+ self._status = status
89
+
90
+ def _get_prio(self):
91
+ return self.PRIORITY_MAP.get(self._status, 0)
92
+
93
+ def __eq__(self, __o: object) -> bool:
94
+ if not isinstance(__o, ComparableTestInvestigationStatus):
95
+ return False
96
+ return self._get_prio() == __o._get_prio()
97
+
98
+ def __ne__(self, __o: object) -> bool:
99
+ if not isinstance(__o, ComparableTestInvestigationStatus):
100
+ return False
101
+ return not self.__eq__(__o)
102
+
103
+ def __lt__(self, __o: object) -> bool:
104
+ if not isinstance(__o, ComparableTestInvestigationStatus):
105
+ return False
106
+ return self._get_prio() < __o._get_prio()
107
+
108
+ def __gt__(self, __o: object) -> bool:
109
+ if not isinstance(__o, ComparableTestInvestigationStatus):
110
+ return False
111
+ return self._get_prio() > __o._get_prio()
112
+
113
+ def __ge__(self, __o: object) -> bool:
114
+ if not isinstance(__o, ComparableTestInvestigationStatus):
115
+ return False
116
+ return self._get_prio() >= __o._get_prio()
117
+
118
+ def __le__(self, __o: object) -> bool:
119
+ if not isinstance(__o, ComparableTestInvestigationStatus):
120
+ return False
121
+ return self._get_prio() <= __o._get_prio()
122
+
123
+
124
+ def generate_field_status_map(
125
+ last_runs: list[TestRunStatRow],
126
+ field_name = "status",
127
+ container_class = TestStatus,
128
+ cmp_class = ComparableTestStatus
129
+ ) -> dict[int, str]:
130
+
131
+ status_map = {}
132
+ for run in last_runs:
133
+ run_number = get_build_number(run["build_job_url"])
134
+ match status := status_map.get(run_number):
135
+ case str():
136
+ if cmp_class(container_class(status)) < cmp_class(container_class(run[field_name])):
137
+ status_map[run_number] = run[field_name]
138
+ case _:
139
+ status_map[run_number] = run[field_name]
140
+ return status_map
141
+
142
+
79
143
  class ReleaseStats:
80
144
  def __init__(self, release: ArgusRelease) -> None:
81
145
  self.release = release
@@ -227,18 +291,6 @@ class TestStats:
227
291
  "hasComments": self.has_comments
228
292
  }
229
293
 
230
- def _generate_status_map(self, last_runs: list[TestRunStatRow]) -> dict[int, str]:
231
- status_map = {}
232
- for run in last_runs:
233
- run_number = get_build_number(run["build_job_url"])
234
- match status := status_map.get(run_number):
235
- case str():
236
- if ComparableTestStatus(TestStatus(status)) < ComparableTestStatus(TestStatus(run["status"])):
237
- status_map[run_number] = run["status"]
238
- case _:
239
- status_map[run_number] = run["status"]
240
- return status_map
241
-
242
294
  def collect(self, limited=False):
243
295
 
244
296
  # TODO: Parametrize run limit
@@ -252,10 +304,12 @@ class TestStats:
252
304
  self.status = TestStatus.NOT_RUN if self.is_scheduled else TestStatus.NOT_PLANNED
253
305
  self.parent_group.increment_status(status=self.status)
254
306
  return
255
- status_map = self._generate_status_map(last_runs)
307
+ status_map = generate_field_status_map(last_runs)
308
+ investigation_status_map = generate_field_status_map(
309
+ last_runs, "investigation_status", TestInvestigationStatus, ComparableTestInvestigationStatus)
256
310
 
257
311
  self.status = status_map.get(get_build_number(last_run["build_job_url"]))
258
- self.investigation_status = TestInvestigationStatus(last_run["investigation_status"])
312
+ self.investigation_status = investigation_status_map.get(get_build_number(last_run["build_job_url"]))
259
313
  self.start_time = last_run["start_time"]
260
314
 
261
315
  self.parent_group.increment_status(status=self.status)
@@ -300,7 +354,7 @@ class ReleaseStatsCollector:
300
354
 
301
355
  if self.release_version:
302
356
  self.release_rows = list(
303
- filter(lambda row: row["scylla_version"] == self.release_version, self.release_rows))
357
+ filter(lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"], self.release_rows))
304
358
 
305
359
  self.release_stats = ReleaseStats(release=self.release)
306
360
  self.release_stats.collect(rows=self.release_rows, limited=limited, force=force)
@@ -30,7 +30,9 @@ from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
30
30
 
31
31
  from argus.backend.plugins.loader import AVAILABLE_PLUGINS
32
32
  from argus.backend.events.event_processors import EVENT_PROCESSORS
33
+ from argus.backend.service.event_service import EventService
33
34
  from argus.backend.service.notification_manager import NotificationManagerService
35
+ from argus.backend.service.stats import ComparableTestStatus
34
36
  from argus.backend.util.common import get_build_number, strip_html_tags
35
37
  from argus.backend.util.enums import TestInvestigationStatus, TestStatus
36
38
 
@@ -80,6 +82,8 @@ class TestRunService:
80
82
  for row in last_runs:
81
83
  row["build_number"] = get_build_number(build_job_url=row["build_job_url"])
82
84
 
85
+ last_runs = sorted(last_runs, reverse=True, key=lambda run: (run["build_number"], ComparableTestStatus(TestStatus(run["status"]))))
86
+
83
87
  return last_runs
84
88
 
85
89
  def get_runs_by_id(self, test_id: UUID, runs: list[UUID]): # FIXME: Not needed, use get_run and individual polling
@@ -108,7 +112,7 @@ class TestRunService:
108
112
  run.status = new_status.value
109
113
  run.save()
110
114
 
111
- self.create_run_event(
115
+ EventService.create_run_event(
112
116
  kind=ArgusEventTypes.TestRunStatusChanged,
113
117
  body={
114
118
  "message": "Status was changed from {old_status} to {new_status} by {username}",
@@ -136,7 +140,7 @@ class TestRunService:
136
140
  run.investigation_status = new_status.value
137
141
  run.save()
138
142
 
139
- self.create_run_event(
143
+ EventService.create_run_event(
140
144
  kind=ArgusEventTypes.TestRunStatusChanged,
141
145
  body={
142
146
  "message": "Investigation status was changed from {old_status} to {new_status} by {username}",
@@ -175,7 +179,7 @@ class TestRunService:
175
179
  LOGGER.warning("Non existent assignee was present on the run %s for test %s: %s",
176
180
  run_id, test_id, old_assignee)
177
181
  old_assignee = None
178
- self.create_run_event(
182
+ EventService.create_run_event(
179
183
  kind=ArgusEventTypes.AssigneeChanged,
180
184
  body={
181
185
  "message": "Assignee was changed from \"{old_user}\" to \"{new_user}\" by {username}",
@@ -244,7 +248,7 @@ class TestRunService:
244
248
  content_params=params
245
249
  )
246
250
 
247
- self.create_run_event(kind=ArgusEventTypes.TestRunCommentPosted, body={
251
+ EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentPosted, body={
248
252
  "message": "A comment was posted by {username}",
249
253
  "username": g.user.username
250
254
  }, user_id=g.user.id, run_id=run_id, release_id=release.id, test_id=test.id)
@@ -257,7 +261,7 @@ class TestRunService:
257
261
  raise Exception("Unable to delete other user comments")
258
262
  comment.delete()
259
263
 
260
- self.create_run_event(kind=ArgusEventTypes.TestRunCommentDeleted, body={
264
+ EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentDeleted, body={
261
265
  "message": "A comment was deleted by {username}",
262
266
  "username": g.user.username
263
267
  }, user_id=g.user.id, run_id=run_id, release_id=comment.release_id, test_id=test_id)
@@ -273,25 +277,13 @@ class TestRunService:
273
277
  comment.mentions = mentions
274
278
  comment.save()
275
279
 
276
- self.create_run_event(kind=ArgusEventTypes.TestRunCommentUpdated, body={
280
+ EventService.create_run_event(kind=ArgusEventTypes.TestRunCommentUpdated, body={
277
281
  "message": "A comment was edited by {username}",
278
282
  "username": g.user.username
279
283
  }, user_id=g.user.id, run_id=run_id, release_id=comment.release_id, test_id=test_id)
280
284
 
281
285
  return self.get_run_comments(run_id=run_id)
282
286
 
283
- def create_run_event(self, kind: ArgusEventTypes, body: dict, user_id=None, run_id=None, release_id=None, group_id=None, test_id=None):
284
- event = ArgusEvent()
285
- event.release_id = release_id
286
- event.group_id = group_id
287
- event.test_id = test_id
288
- event.user_id = user_id
289
- event.run_id = run_id
290
- event.body = json.dumps(body, ensure_ascii=True, separators=(',', ':'))
291
- event.kind = kind.value
292
- event.created_at = datetime.utcnow()
293
- event.save()
294
-
295
287
  def get_run_events(self, run_id: UUID):
296
288
  response = {}
297
289
  all_events = ArgusEvent.filter(run_id=run_id).all()
@@ -358,7 +350,7 @@ class TestRunService:
358
350
  new_issue.last_status = issue_state.get("state")
359
351
  new_issue.save()
360
352
 
361
- self.create_run_event(
353
+ EventService.create_run_event(
362
354
  kind=ArgusEventTypes.TestRunIssueAdded,
363
355
  body={
364
356
  "message": "An issue titled \"{title}\" was added by {username}",
@@ -409,7 +401,7 @@ class TestRunService:
409
401
  def delete_github_issue(self, issue_id: UUID) -> dict:
410
402
  issue: ArgusGithubIssue = ArgusGithubIssue.get(id=issue_id)
411
403
 
412
- self.create_run_event(
404
+ EventService.create_run_event(
413
405
  kind=ArgusEventTypes.TestRunIssueRemoved,
414
406
  body={
415
407
  "message": "An issue titled \"{title}\" was removed by {username}",
@@ -448,7 +440,7 @@ class TestRunService:
448
440
  run.status = TestStatus.ABORTED.value
449
441
  run.save()
450
442
 
451
- self.create_run_event(
443
+ EventService.create_run_event(
452
444
  kind=ArgusEventTypes.TestRunStatusChanged,
453
445
  body={
454
446
  "message": "Run was automatically terminated due to not responding for more than 45 minutes "
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime
2
+ import logging
2
3
  from json.encoder import JSONEncoder
3
4
  from uuid import UUID
4
5
 
@@ -6,6 +7,8 @@ import cassandra.cqlengine.usertype as ut
6
7
  import cassandra.cqlengine.models as m
7
8
 
8
9
 
10
+ LOGGER = logging.getLogger(__name__)
11
+
9
12
  class ArgusJSONEncoder(JSONEncoder):
10
13
  def default(self, o):
11
14
  match o:
@@ -16,6 +16,7 @@ from argus.backend.plugins.driver_matrix_tests.raw_types import RawMatrixTestRes
16
16
 
17
17
  LOGGER = logging.getLogger(__name__)
18
18
 
19
+ TestTypeType = Literal['java', 'cpp', 'python', 'gocql']
19
20
 
20
21
  class AdaptedXUnitData(TypedDict):
21
22
  timestamp: str
@@ -52,6 +53,14 @@ def cpp_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
52
53
  }
53
54
 
54
55
 
56
+ def gocql_driver_matrix_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
57
+ testsuites = list(xml.getroot().iter("testsuite"))
58
+
59
+ return {
60
+ "timestamp": testsuites[0].attrib.get("timestamp"),
61
+ }
62
+
63
+
55
64
  def generic_adapter(xml: ElementTree.ElementTree) -> AdaptedXUnitData:
56
65
  return {
57
66
  "timestamp": datetime.utcnow().isoformat()
@@ -66,6 +75,7 @@ class ArgusDriverMatrixClient(ArgusClient):
66
75
  "java": java_driver_matrix_adapter,
67
76
  "cpp": cpp_driver_matrix_adapter,
68
77
  "python": python_driver_matrix_adapter,
78
+ "gocql": gocql_driver_matrix_adapter,
69
79
  }
70
80
 
71
81
  def __init__(self, run_id: UUID, auth_token: str, base_url: str, api_version="v1") -> None:
@@ -107,8 +117,11 @@ class ArgusDriverMatrixClient(ArgusClient):
107
117
 
108
118
  return raw_cases
109
119
 
110
- def get_driver_info(self, xml_name: str) -> dict[str, str]:
111
- filename_re = r"(?P<name>[\w]*)\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[\d\.]*)\.xml"
120
+ def get_driver_info(self, xml_name: str, test_type: TestTypeType) -> dict[str, str]:
121
+ if test_type == "cpp":
122
+ filename_re = r"TEST-(?P<driver_name>[\w]*)-(?P<version>[\d\.]*)\.xml"
123
+ else:
124
+ filename_re = r"(?P<name>[\w]*)\.(?P<driver_name>[\w]*)\.(?P<proto>v\d)\.(?P<version>[\d\.]*)\.xml"
112
125
 
113
126
  match = re.match(filename_re, xml_name)
114
127
 
@@ -124,14 +137,14 @@ class ArgusDriverMatrixClient(ArgusClient):
124
137
 
125
138
  return total - errors - skipped - failures
126
139
 
127
- def parse_result_xml(self, xml_path: Path, test_type: Literal['java', 'cpp', 'python']) -> RawMatrixTestResult:
140
+ def parse_result_xml(self, xml_path: Path, test_type: TestTypeType) -> RawMatrixTestResult:
128
141
  with xml_path.open(mode="rt", encoding="utf-8") as xml_file:
129
142
  xml = ElementTree.parse(source=xml_file)
130
143
  LOGGER.info("%s", pformat(xml))
131
144
  testsuites = xml.getroot()
132
145
  adapted_data = self.TEST_ADAPTERS.get(test_type, generic_adapter)(xml)
133
146
 
134
- driver_info = self.get_driver_info(xml_path.name)
147
+ driver_info = self.get_driver_info(xml_path.name, test_type)
135
148
  test_collection = {
136
149
  "timestamp": adapted_data["timestamp"],
137
150
  "name": xml_path.stem,
@@ -171,7 +184,7 @@ class ArgusDriverMatrixClient(ArgusClient):
171
184
  "suites": all_suites
172
185
  }
173
186
 
174
- def get_results(self, result_path: str, test_type: Literal['java', 'cpp', 'python']) -> list[RawMatrixTestResult]:
187
+ def get_results(self, result_path: str, test_type: TestTypeType) -> list[RawMatrixTestResult]:
175
188
  xmls = glob(f"{result_path}/**/*.xml", recursive=True)
176
189
  LOGGER.info("Will use following XMLs: %s", pformat(xmls))
177
190
  results = []
@@ -180,7 +193,7 @@ class ArgusDriverMatrixClient(ArgusClient):
180
193
  results.append(result)
181
194
  return results
182
195
 
183
- def submit(self, build_id: str, build_url: str, env_path: str, result_path: str, test_type: Literal['java', 'cpp', 'python']):
196
+ def submit(self, build_id: str, build_url: str, env_path: str, result_path: str, test_type: TestTypeType):
184
197
  env = self.parse_build_environment(env_path)
185
198
  results = self.get_results(result_path, test_type)
186
199
 
@@ -1,5 +1,6 @@
1
1
  from uuid import UUID
2
2
  from dataclasses import asdict
3
+ from argus.backend.plugins.sct.types import GeminiResultsRequest, PerformanceResultsRequest
3
4
  from argus.backend.util.enums import ResourceState, TestStatus
4
5
  from argus.client.base import ArgusClient
5
6
  from argus.client.sct.types import EventsInfo, LogLink, Package
@@ -17,6 +18,8 @@ class ArgusSCTClient(ArgusClient):
17
18
  SET_SCT_RUNNER = "/sct/$id/sct_runner/set"
18
19
  UPDATE_SHARDS_FOR_RESOURCE = "/sct/$id/resource/$name/shards"
19
20
  SUBMIT_NEMESIS = "/sct/$id/nemesis/submit"
21
+ SUBMIT_GEMINI_RESULTS = "/sct/$id/gemini/submit"
22
+ SUBMIT_PERFORMANCE_RESULTS = "/sct/$id/performance/submit"
20
23
  FINALIZE_NEMESIS = "/sct/$id/nemesis/finalize"
21
24
  SUBMIT_EVENTS = "/sct/$id/events/submit"
22
25
 
@@ -113,6 +116,34 @@ class ArgusSCTClient(ArgusClient):
113
116
  )
114
117
  self.check_response(response)
115
118
 
119
+ def submit_gemini_results(self, gemini_data: GeminiResultsRequest) -> None:
120
+ """
121
+ Submits gemini results such as oracle node information and gemini command & its results
122
+ """
123
+ response = self.post(
124
+ endpoint=self.Routes.SUBMIT_GEMINI_RESULTS,
125
+ location_params={"id": str(self.run_id)},
126
+ body={
127
+ **self.generic_body,
128
+ "gemini_data": gemini_data,
129
+ }
130
+ )
131
+ self.check_response(response)
132
+
133
+ def submit_performance_results(self, performance_results: PerformanceResultsRequest) -> None:
134
+ """
135
+ Submits results of a performance run. Things such as throughput stats, overall and per op
136
+ """
137
+ response = self.post(
138
+ endpoint=self.Routes.SUBMIT_PERFORMANCE_RESULTS,
139
+ location_params={"id": str(self.run_id)},
140
+ body={
141
+ **self.generic_body,
142
+ "performance_results": performance_results,
143
+ }
144
+ )
145
+ self.check_response(response)
146
+
116
147
  def create_resource(self, name: str, resource_type: str, public_ip: str, private_ip: str,
117
148
  region: str, provider: str, shards_amount: int, state=ResourceState.RUNNING) -> None:
118
149
  """
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "argus-alm"
3
- version = "0.11.4"
3
+ version = "0.11.6"
4
4
  description = "Argus"
5
5
  authors = ["Alexey Kartashov <alexey.kartashov@scylladb.com>"]
6
6
  license = "Apache-2.0"
argus_alm-0.11.4/setup.py DELETED
@@ -1,45 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['argus',
6
- 'argus.backend',
7
- 'argus.backend.controller',
8
- 'argus.backend.events',
9
- 'argus.backend.models',
10
- 'argus.backend.plugins',
11
- 'argus.backend.plugins.driver_matrix_tests',
12
- 'argus.backend.plugins.sct',
13
- 'argus.backend.plugins.sirenada',
14
- 'argus.backend.service',
15
- 'argus.backend.util',
16
- 'argus.client',
17
- 'argus.client.driver_matrix_tests',
18
- 'argus.client.sct',
19
- 'argus.client.sirenada',
20
- 'argus.db']
21
-
22
- package_data = \
23
- {'': ['*']}
24
-
25
- install_requires = \
26
- ['requests>=2.26.0,<3.0.0']
27
-
28
- setup_kwargs = {
29
- 'name': 'argus-alm',
30
- 'version': '0.11.4',
31
- 'description': 'Argus',
32
- 'long_description': '# Argus\n\n## Description\n\nArgus is a test tracking system intended to provide observability into automated test pipelines which use long-running resources. It allows observation of a test status, its events and its allocated resources. It also allows easy comparison between particular runs of a specific test.\n\n## Installation notes\n\n### Prerequisites\n\n- Python >=3.10.0 (system-wide or pyenv)\n\n- NodeJS >=16 (with npm)\n\n- Yarn (can be installed globally with `npm -g install yarn`)\n\n- nginx\n\n- poetry >=1.2.0b1\n\n### From source\n\n#### Production\n\nPerform the following steps:\n\nCreate a user that will be used by uwsgi:\n\n```bash\nuseradd -m -s /bin/bash argus\nsudo -iu argus\n```\n\n(Optional) Install pyenv and create a virtualenv for this user:\n\n```bash\npyenv install 3.10.0\npyenv virtualenv argus\npyenv activate argus\n```\n\nClone the project into a directory somewhere where user has full write permissions\n\n```bash\ngit clone https://github.com/scylladb/argus ~/app\ncd ~/app\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`\n\n```bash\nyarn webpack\n```\n\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* variables with their respective values.\n\nCopy nginx configuration file from `docs/configs/argus.nginx.conf` to nginx virtual hosts directory:\n\nUbuntu:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/sites-available/argus\nsudo ln -s /etc/nginx/sites-enabled/argus /etc/nginx/sites-available/argus\n```\n\nRHEL/Centos/Alma/Fedora:\n\n```bash\nsudo cp docs/configs/argus.nginx.conf /etc/nginx/conf.d/argus.conf\n```\n\nAdjust the webhost settings in that file as necessary, particularly `listen` and `server_name` directives.\n\nCopy systemd service file from `docs/config/argus.service` to `/etc/systemd/system` directory:\n\n```bash\nsudo cp docs/config/argus.service /etc/systemd/system\n```\n\nOpen it and adjust the path to the `start_argus.sh` script in the `ExecStart=` directive and the user/group, then reload systemd daemon configuration and enable (and optionally start) the service.\n\nWARNING: `start_argus.sh` assumes pyenv is installed into `~/.pyenv`\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now argus.service\n```\n\n#### Development\n\nClone the project into a directory somewhere\n\n```bash\ngit clone https://github.com/scylladb/argus\ncd argus\n```\n\nInstall project dependencies:\n\n```bash\npoetry install --with default,dev,web-backend,docker-image\nyarn install\n```\n\nCompile frontend files from `/frontend` into `/public/dist`. Add --watch to recompile files on change.\n\n```bash\nyarn webpack --watch\n```\n##### Configuration\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\nSee `Production` section for more details.\nTo configure Github authentication follow steps:\n1. Authorize OAuth App\n 1. go to your Account Settings (top right corner) -> Developer settings (left pane) -> OAuth Apps\n 2. Click Create New OAuth App button\n 3. Fill the fields (app name: `argus-dev`, homepage URL `http://localhost:5000`, Auth callback URL: `http://localhost:5000/profile/oauth/github`)\n 4. Confirm and get the tokens/ids required for config\n2. Create Jenkins token for your account\n 1. Go to `Configure` in top right corner\n 2. Click `Add new Token`\n 3. Get it and paste to config to `JENKINS_API_TOKEN` param\n##### Database Initialization\n\nYou can initialize a scylla cluster in any way you like, either using docker image with docker-compose or using cassandra cluster manager. You will need to create the keyspace manually before you can sync database models.\n\nCreate keyspace according to your configuration.\ne.g. (need to test if it works with RF=1 if not, make it 3)\n```\nCREATE KEYSPACE argus WITH replication = {\'class\': \'SimpleStrategy\', \'replication_factor\' : 1}\n```\n\nInitial sync can be done as follows:\n\n```py\nfrom argus.backend.db import ScyllaCluster\nfrom argus.db.testrun import TestRun\ndb = ScyllaCluster.get()\n\ndb.sync_models() # Syncronizes Object Mapper models\nTestRun.init_own_table() # Syncronizes TestRun table (separate from python-driver Object Mapper)\n\n```\n\nYou can also use `flask sync-models` afterwards during development when making small changes to models.\n\nIt is recommended to set up jenkins api key and run `flask scan-jenkins` afterwards to get basic release/group/test structure.\n\nThere are scripts in `./scripts` directory that can be used to download data from production, upload them into your dev db and fix their relations to other models in your instance of the application. Specifically, `download_runs_from_prod.py` requires additional config, `argus.local.prod.yaml` which is the config used to connect to the production cluster. The scripts are split to prevent mistakes and accidentally affecting production cluster.\n\n##### Configuration\n\nCreate a `argus.local.yaml` configuration file (used to configure database connection) and a `argus_web.yaml` (used for webapp secrets) in your application install directory.\n\n```bash\ncp argus_web.example.yaml argus_web.yaml\ncp argus.yaml argus.local.yaml\n```\n\nOpen `argus.local.yaml` and add the database connection information (contact_points, user, password and keyspace name).\n\nOpen `argus_web.yaml` and change the `SECRET_KEY` value to something secure, like a sha512 digest of random bytes. Fill out GITHUB_* and JENKINS_* variables with their respective values.\n\nRun the application from CLI using:\n\n```bash\nFLASK_ENV="development" FLASK_APP="argus_backend:start_server" FLASK_DEBUG=1 CQLENG_ALLOW_SCHEMA_MANAGEMENT=1 flask run\n```\n\nOmit `FLASK_DEBUG` if running your own debugger (pdb, pycharm, vscode)\n',
33
- 'author': 'Alexey Kartashov',
34
- 'author_email': 'alexey.kartashov@scylladb.com',
35
- 'maintainer': 'None',
36
- 'maintainer_email': 'None',
37
- 'url': 'https://github.com/scylladb/argus',
38
- 'packages': packages,
39
- 'package_data': package_data,
40
- 'install_requires': install_requires,
41
- 'python_requires': '>=3.10,<4.0',
42
- }
43
-
44
-
45
- setup(**setup_kwargs)
File without changes
File without changes
File without changes
File without changes
File without changes