argus-alm 0.11.3__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 (78) hide show
  1. {argus_alm-0.11.3 → argus_alm-0.11.6}/PKG-INFO +1 -1
  2. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/api.py +12 -0
  3. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/main.py +1 -3
  4. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/team.py +1 -1
  5. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/testrun_api.py +16 -0
  6. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/events/event_processors.py +1 -0
  7. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/models/web.py +1 -0
  8. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/core.py +17 -1
  9. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/model.py +5 -1
  10. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sct/controller.py +47 -0
  11. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sct/plugin.py +3 -1
  12. argus_alm-0.11.6/argus/backend/plugins/sct/service.py +393 -0
  13. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sct/testrun.py +78 -21
  14. argus_alm-0.11.6/argus/backend/plugins/sct/types.py +38 -0
  15. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sct/udt.py +13 -0
  16. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/argus_service.py +16 -32
  17. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/build_system_monitor.py +21 -3
  18. argus_alm-0.11.6/argus/backend/service/event_service.py +18 -0
  19. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/stats.py +69 -15
  20. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/testrun.py +59 -21
  21. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/encoders.py +3 -0
  22. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/enums.py +1 -0
  23. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/driver_matrix_tests/client.py +19 -6
  24. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/sct/client.py +50 -4
  25. {argus_alm-0.11.3 → argus_alm-0.11.6}/pyproject.toml +1 -1
  26. argus_alm-0.11.3/argus/backend/plugins/sct/service.py +0 -219
  27. argus_alm-0.11.3/setup.py +0 -45
  28. {argus_alm-0.11.3 → argus_alm-0.11.6}/LICENSE +0 -0
  29. {argus_alm-0.11.3 → argus_alm-0.11.6}/README.md +0 -0
  30. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/__init__.py +0 -0
  31. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/.gitkeep +0 -0
  32. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/__init__.py +0 -0
  33. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/cli.py +0 -0
  34. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/__init__.py +0 -0
  35. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/admin.py +0 -0
  36. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/admin_api.py +0 -0
  37. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/auth.py +0 -0
  38. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/client_api.py +0 -0
  39. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/notification_api.py +0 -0
  40. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/notifications.py +0 -0
  41. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/controller/team_ui.py +0 -0
  42. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/db.py +0 -0
  43. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/error_handlers.py +0 -0
  44. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/models/__init__.py +0 -0
  45. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/__init__.py +0 -0
  46. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/controller.py +0 -0
  47. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/plugin.py +0 -0
  48. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -0
  49. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/driver_matrix_tests/udt.py +0 -0
  50. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/loader.py +0 -0
  51. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sct/resource_setup.py +0 -0
  52. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/model.py +0 -0
  53. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/plugin.py +0 -0
  54. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/plugins/sirenada/types.py +0 -0
  55. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/admin.py +0 -0
  56. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/client_service.py +0 -0
  57. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/notification_manager.py +0 -0
  58. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/release_manager.py +0 -0
  59. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/team_manager_service.py +0 -0
  60. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/service/user.py +0 -0
  61. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/template_filters.py +0 -0
  62. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/common.py +0 -0
  63. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/config.py +0 -0
  64. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/logsetup.py +0 -0
  65. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/module_loaders.py +0 -0
  66. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/backend/util/send_email.py +0 -0
  67. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/__init__.py +0 -0
  68. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/base.py +0 -0
  69. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/sct/types.py +0 -0
  70. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/client/sirenada/client.py +0 -0
  71. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/.gitkeep +0 -0
  72. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/argus_json.py +0 -0
  73. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/cloud_types.py +0 -0
  74. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/config.py +0 -0
  75. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/db_types.py +0 -0
  76. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/interface.py +0 -0
  77. {argus_alm-0.11.3 → argus_alm-0.11.6}/argus/db/testrun.py +0 -0
  78. {argus_alm-0.11.3 → 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.3
3
+ Version: 0.11.6
4
4
  Summary: Argus
5
5
  Home-page: https://github.com/scylladb/argus
6
6
  License: Apache-2.0
@@ -472,3 +472,15 @@ def resolve_artifact_size():
472
472
  "artifactSize": length,
473
473
  }
474
474
  }
475
+
476
+
477
+ @bp.route("/user/jobs")
478
+ @api_login_required
479
+ def user_jobs():
480
+ service = ArgusService()
481
+ result = list(service.get_jobs_for_user(user=g.user))
482
+
483
+ return {
484
+ "status": "ok",
485
+ "response": result
486
+ }
@@ -241,9 +241,7 @@ def update_password():
241
241
  @bp.route("/profile/jobs", methods=["GET"])
242
242
  @login_required
243
243
  def profile_jobs():
244
- service = ArgusService()
245
- jobs = service.get_jobs_for_user(g.user)
246
- return render_template("profile_jobs.html.j2", runs=jobs)
244
+ return render_template("profile_jobs.html.j2")
247
245
 
248
246
 
249
247
  @bp.route("/profile/schedules", methods=["GET"])
@@ -107,7 +107,7 @@ def user_teams(user_id: str):
107
107
  @api_login_required
108
108
  def user_jobs(user_id: str):
109
109
  user = User.get(id=UUID(user_id))
110
- result = ArgusService().get_jobs_for_user(user)
110
+ result = list(ArgusService().get_jobs_for_user(user))
111
111
 
112
112
  return {
113
113
  "status": "ok",
@@ -269,3 +269,19 @@ def sct_terminate_stuck_runs():
269
269
  "total": result
270
270
  }
271
271
  }
272
+
273
+
274
+ @bp.route("/ignore_jobs", methods=["POST"])
275
+ @api_login_required
276
+ def ignore_jobs():
277
+ payload = get_payload(request)
278
+ service = TestRunService()
279
+
280
+ result = service.ignore_jobs(test_id=payload["testId"], reason=payload["reason"])
281
+
282
+ return {
283
+ "status": "ok",
284
+ "response": {
285
+ "affectedJobs": result
286
+ }
287
+ }
@@ -30,4 +30,5 @@ EVENT_PROCESSORS = {
30
30
  ArgusEventTypes.TestRunIssueAdded: event_process_issue_added,
31
31
  ArgusEventTypes.TestRunIssueRemoved: event_process_issue_added,
32
32
  ArgusEventTypes.TestRunInvestigationStatusChanged: event_process_investigation_status_changed,
33
+ ArgusEventTypes.TestRunBatchInvestigationStatusChange: event_process_investigation_status_changed,
33
34
  }
@@ -190,6 +190,7 @@ class ArgusEventTypes(str, Enum):
190
190
  AssigneeChanged = "ARGUS_ASSIGNEE_CHANGE"
191
191
  TestRunStatusChanged = "ARGUS_TEST_RUN_STATUS_CHANGE"
192
192
  TestRunInvestigationStatusChanged = "ARGUS_TEST_RUN_INVESTIGATION_STATUS_CHANGE"
193
+ TestRunBatchInvestigationStatusChange = "ARGUS_TEST_RUN_INVESTIGATION_BATCH_STATUS_CHANGE"
193
194
  TestRunCommentPosted = "ARGUS_TEST_RUN_COMMENT_POSTED"
194
195
  TestRunCommentUpdated = "ARGUS_TEST_RUN_COMMENT_UPDATED"
195
196
  TestRunCommentDeleted = "ARGUS_TEST_RUN_COMMENT_DELETED"
@@ -109,11 +109,27 @@ class PluginModelBase(Model):
109
109
  def get_jobs_assigned_to_user(cls, user: User):
110
110
  cluster = ScyllaCluster.get()
111
111
  query = cluster.prepare("SELECT build_id, start_time, release_id, group_id, assignee, "
112
- f"test_id, id, status, investigation_status, build_job_url FROM {cls.table_name()} WHERE assignee = ?")
112
+ f"test_id, id, status, investigation_status, build_job_url, scylla_version FROM {cls.table_name()} WHERE assignee = ?")
113
113
  rows = cluster.session.execute(query=query, parameters=(user.id,))
114
114
 
115
115
  return list(rows)
116
116
 
117
+ @classmethod
118
+ def get_jobs_meta_by_test_id(cls, test_id: UUID):
119
+ cluster = ScyllaCluster.get()
120
+ query = cluster.prepare(f"SELECT build_id, start_time, id, test_id, release_id, group_id, status, investigation_status FROM {cls.table_name()} WHERE test_id = ?")
121
+ rows = cluster.session.execute(query=query, parameters=(test_id,))
122
+
123
+ return list(rows)
124
+
125
+ @classmethod
126
+ def prepare_investigation_status_update_query(cls, build_id: str, start_time: datetime, new_status: TestInvestigationStatus):
127
+ cluster = ScyllaCluster.get()
128
+ query = cluster.prepare(f"UPDATE {cls.table_name()} SET investigation_status = ? WHERE build_id = ? AND start_time = ?")
129
+ bound_query = query.bind(values=(new_status.value, build_id, start_time))
130
+
131
+ return bound_query
132
+
117
133
  @classmethod
118
134
  def get_stats_for_release(cls, release: ArgusRelease):
119
135
  cluster = ScyllaCluster.get()
@@ -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
 
@@ -31,6 +31,23 @@ def sct_submit_screenshots(run_id: str):
31
31
  }
32
32
 
33
33
 
34
+ @bp.route("/<string:run_id>/sct_runner/set", methods=["POST"])
35
+ @api_login_required
36
+ def sct_set_runner(run_id: str):
37
+ payload = get_payload(request)
38
+ result = SCTService.set_sct_runner(
39
+ run_id=run_id,
40
+ public_ip=payload["public_ip"],
41
+ private_ip=payload["private_ip"],
42
+ region=payload["region"],
43
+ backend=payload["backend"]
44
+ )
45
+ return {
46
+ "status": "ok",
47
+ "response": result
48
+ }
49
+
50
+
34
51
  @bp.route("/<string:run_id>/resource/create", methods=["POST"])
35
52
  @api_login_required
36
53
  def sct_resource_create(run_id: str):
@@ -97,6 +114,36 @@ def sct_events_submit(run_id: str):
97
114
  }
98
115
 
99
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
+
100
147
  @bp.route("/release/<string:release_name>/kernels", methods=["GET"])
101
148
  @api_login_required
102
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
  ]
@@ -0,0 +1,393 @@
1
+ from dataclasses import dataclass
2
+ from functools import reduce
3
+ import logging
4
+ import math
5
+ from time import time
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
10
+ from argus.backend.plugins.sct.udt import (
11
+ CloudInstanceDetails,
12
+ CloudResource,
13
+ EventsBySeverity,
14
+ NemesisRunInfo,
15
+ NodeDescription,
16
+ PackageVersion,
17
+ PerformanceHDRHistogram,
18
+ )
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
22
+
23
+ LOGGER = logging.getLogger(__name__)
24
+
25
+
26
+ class SCTServiceException(Exception):
27
+ pass
28
+
29
+
30
+ @dataclass(init=True, repr=True)
31
+ class NemesisSubmissionRequest:
32
+ name: str
33
+ class_name: str
34
+ start_time: int
35
+ node_name: str
36
+ node_ip: str
37
+ node_shards: int
38
+
39
+
40
+ @dataclass(init=True, repr=True)
41
+ class NemesisFinalizationRequest:
42
+ name: str
43
+ start_time: int
44
+ status: str
45
+ message: str
46
+
47
+
48
+ @dataclass(init=True, repr=True)
49
+ class EventSubmissionRequest:
50
+ severity: str
51
+ total_events: int
52
+ messages: list[str]
53
+
54
+
55
+ class SCTService:
56
+
57
+ @staticmethod
58
+ def submit_packages(run_id: str, packages: list[dict]) -> str:
59
+ try:
60
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
61
+ for package_dict in packages:
62
+ package = PackageVersion(**package_dict)
63
+ run.packages.append(package)
64
+ run.save()
65
+ except SCTTestRun.DoesNotExist as exception:
66
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
67
+ raise SCTServiceException("Run not found", run_id) from exception
68
+
69
+ return "added"
70
+
71
+
72
+ @staticmethod
73
+ def set_sct_runner(run_id: str, public_ip: str, private_ip: str, region: str, backend: str):
74
+ try:
75
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
76
+ run.sct_runner_host = CloudInstanceDetails(
77
+ public_ip=public_ip,
78
+ private_ip=private_ip,
79
+ provider=backend,
80
+ region=region,
81
+ )
82
+ run.save()
83
+ except SCTTestRun.DoesNotExist as exception:
84
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
85
+ raise SCTServiceException("Run not found", run_id) from exception
86
+
87
+ return "updated"
88
+
89
+ @staticmethod
90
+ def submit_screenshots(run_id: str, screenshot_links: list[str]) -> str:
91
+ try:
92
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
93
+ for link in screenshot_links:
94
+ run.add_screenshot(link)
95
+ run.save()
96
+ except SCTTestRun.DoesNotExist as exception:
97
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
98
+ raise SCTServiceException("Run not found", run_id) from exception
99
+
100
+ return "submitted"
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
+
250
+ @staticmethod
251
+ def create_resource(run_id: str, resource_details: dict) -> str:
252
+ instance_details = CloudInstanceDetails(**resource_details.pop("instance_details"))
253
+ resource = CloudResource(**resource_details, instance_info=instance_details)
254
+ try:
255
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
256
+ run.get_resources().append(resource)
257
+ run.save()
258
+ except SCTTestRun.DoesNotExist as exception:
259
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
260
+ raise SCTServiceException("Run not found", run_id) from exception
261
+
262
+ return "created"
263
+
264
+ @staticmethod
265
+ def update_resource_shards(run_id: str, resource_name: str, new_shards: int) -> str:
266
+ try:
267
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
268
+ resource = next(res for res in run.get_resources() if res.name == resource_name)
269
+ resource.get_instance_info().shards_amount = new_shards
270
+ run.save()
271
+ except StopIteration as exception:
272
+ LOGGER.error("Resource %s not found in run %s", resource_name, run_id)
273
+ raise SCTServiceException("Resource not found", resource_name) from exception
274
+ except SCTTestRun.DoesNotExist as exception:
275
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
276
+ raise SCTServiceException("Run not found", run_id) from exception
277
+
278
+ return "updated"
279
+
280
+ @staticmethod
281
+ def terminate_resource(run_id: str, resource_name: str, reason: str) -> str:
282
+ try:
283
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
284
+ resource = next(res for res in run.get_resources() if res.name == resource_name)
285
+ resource.get_instance_info().termination_reason = reason
286
+ resource.get_instance_info().termination_time = int(time())
287
+ resource.state = ResourceState.TERMINATED.value
288
+ run.save()
289
+ except StopIteration as exception:
290
+ LOGGER.error("Resource %s not found in run %s", resource_name, run_id)
291
+ raise SCTServiceException("Resource not found", resource_name) from exception
292
+ except SCTTestRun.DoesNotExist as exception:
293
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
294
+ raise SCTServiceException("Run not found", run_id) from exception
295
+
296
+ return "terminated"
297
+
298
+ @staticmethod
299
+ def submit_nemesis(run_id: str, nemesis_details: dict) -> str:
300
+ nem_req = NemesisSubmissionRequest(**nemesis_details)
301
+ node_desc = NodeDescription(name=nem_req.node_name, ip=nem_req.node_ip, shards=nem_req.node_shards)
302
+ nemesis_info = NemesisRunInfo(
303
+ class_name=nem_req.class_name,
304
+ name=nem_req.name,
305
+ start_time=int(nem_req.start_time),
306
+ end_time=0,
307
+ duration=0,
308
+ stack_trace="",
309
+ status=NemesisStatus.RUNNING.value,
310
+ target_node=node_desc,
311
+ )
312
+ try:
313
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
314
+ run.add_nemesis(nemesis_info)
315
+ run.save()
316
+ except SCTTestRun.DoesNotExist as exception:
317
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
318
+ raise SCTServiceException("Run not found", run_id) from exception
319
+
320
+ return "created"
321
+
322
+ @staticmethod
323
+ def finalize_nemesis(run_id: str, nemesis_details: dict) -> str:
324
+ nem_req = NemesisFinalizationRequest(**nemesis_details)
325
+ try:
326
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
327
+ nemesis = next(nem for nem in run.get_nemeses() if nem.name ==
328
+ nem_req.name and nem.start_time == nem_req.start_time)
329
+ nemesis.status = NemesisStatus(nem_req.status).value
330
+ nemesis.stack_trace = nem_req.message
331
+ nemesis.end_time = int(time())
332
+ run.save()
333
+ except StopIteration as exception:
334
+ LOGGER.error("Nemesis %s (%s) not found for run %s", nem_req.name, nem_req.start_time, run_id)
335
+ raise SCTServiceException("Nemesis not found", (nem_req.name, nem_req.start_time)) from exception
336
+ except SCTTestRun.DoesNotExist as exception:
337
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
338
+ raise SCTServiceException("Run not found", run_id) from exception
339
+
340
+ return "updated"
341
+
342
+ @staticmethod
343
+ def submit_events(run_id: str, events: list[dict]) -> str:
344
+ wrapped_events = [EventSubmissionRequest(**ev) for ev in events]
345
+ try:
346
+ run: SCTTestRun = SCTTestRun.get(id=run_id)
347
+ for event in wrapped_events:
348
+ wrapper = EventsBySeverity(severity=event.severity,
349
+ event_amount=event.total_events, last_events=event.messages)
350
+ run.get_events().append(wrapper)
351
+ run.save()
352
+ except SCTTestRun.DoesNotExist as exception:
353
+ LOGGER.error("Run %s not found for SCTTestRun", run_id)
354
+ raise SCTServiceException("Run not found", run_id) from exception
355
+
356
+ return "added"
357
+
358
+ @staticmethod
359
+ def get_scylla_version_kernels_report(release_name: str):
360
+ all_release_runs = SCTTestRun.get_version_data_for_release(release_name=release_name)
361
+ kernels_by_version = {}
362
+ kernel_metadata = {}
363
+ for run in all_release_runs:
364
+ packages = run["packages"]
365
+ if not packages:
366
+ continue
367
+ scylla_pkgs = {p["name"]: p for p in packages if "scylla-server" in p["name"]}
368
+ scylla_pkg = scylla_pkgs["scylla-server-upgraded"] if scylla_pkgs.get(
369
+ "scylla-server-upgraded") else scylla_pkgs.get("scylla-server")
370
+ version = f"{scylla_pkg['version']}-{scylla_pkg['date']}.{scylla_pkg['revision_id']}" if scylla_pkgs else "unknown"
371
+ kernel_packages = [p for p in packages if "kernel" in p["name"]]
372
+ kernel_package = kernel_packages[0] if len(kernel_packages) > 0 else None
373
+ if not kernel_package:
374
+ continue
375
+ version_list = set(kernels_by_version.get(version, []))
376
+ version_list.add(kernel_package["version"])
377
+ kernels_by_version[version] = list(version_list)
378
+ metadata = kernel_metadata.get(
379
+ kernel_package.version,
380
+ {
381
+ "passed": 0,
382
+ "failed": 0,
383
+ "aborted": 0,
384
+ }
385
+ )
386
+ if run["status"] in ["passed", "failed", "aborted"]:
387
+ metadata[run["status"]] += 1
388
+ kernel_metadata[kernel_package["version"]] = metadata
389
+
390
+ return {
391
+ "versions": kernels_by_version,
392
+ "metadata": kernel_metadata
393
+ }