argus-alm 0.11.7__py3-none-any.whl → 0.12.1__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.
@@ -6,7 +6,7 @@ from flask import (
6
6
  Request,
7
7
  )
8
8
  from argus.backend.error_handlers import handle_api_exception
9
- from argus.backend.service.release_manager import ReleaseManagerService
9
+ from argus.backend.service.release_manager import ReleaseEditPayload, ReleaseManagerService
10
10
  from argus.backend.service.user import api_login_required, check_roles
11
11
  from argus.backend.models.web import UserRoles
12
12
 
@@ -92,6 +92,36 @@ def set_release_dormancy():
92
92
  }
93
93
 
94
94
 
95
+ @bp.route("/release/edit", methods=["POST"])
96
+ @check_roles(UserRoles.Admin)
97
+ @api_login_required
98
+ def edit_release():
99
+ payload: ReleaseEditPayload = get_payload(request)
100
+ result = ReleaseManagerService().edit_release(payload)
101
+
102
+ return {
103
+ "status": "ok",
104
+ "response": {
105
+ "updated": result
106
+ }
107
+ }
108
+
109
+
110
+ @bp.route("/release/delete", methods=["POST"])
111
+ @check_roles(UserRoles.Admin)
112
+ @api_login_required
113
+ def delete_release():
114
+ payload = get_payload(request)
115
+ result = ReleaseManagerService().delete_release(release_id=payload["releaseId"])
116
+
117
+ return {
118
+ "status": "ok",
119
+ "response": {
120
+ "deleted": result
121
+ }
122
+ }
123
+
124
+
95
125
  @bp.route("/group/create", methods=["POST"])
96
126
  @check_roles(UserRoles.Admin)
97
127
  @api_login_required
@@ -389,9 +389,10 @@ def release_stats_v2():
389
389
  release = request.args.get("release")
390
390
  limited = bool(int(request.args.get("limited")))
391
391
  version = request.args.get("productVersion", None)
392
+ include_no_version = bool(int(request.args.get("includeNoVersion", True)))
392
393
  force = bool(int(request.args.get("force")))
393
394
  stats = ReleaseStatsCollector(
394
- release_name=release, release_version=version).collect(limited=limited, force=force)
395
+ release_name=release, release_version=version).collect(limited=limited, force=force, include_no_version=include_no_version)
395
396
 
396
397
  res = jsonify({
397
398
  "status": "ok",
@@ -81,7 +81,11 @@ def run_submit_logs(run_type: str, run_id: str):
81
81
  @bp.route("/testrun/<string:run_type>/<string:run_id>/finalize", methods=["POST"])
82
82
  @api_login_required
83
83
  def run_finalize(run_type: str, run_id: str):
84
- result = ClientService().finish_run(run_type=run_type, run_id=run_id)
84
+ try:
85
+ payload = get_payload(request)
86
+ except Exception:
87
+ payload = None
88
+ result = ClientService().finish_run(run_type=run_type, run_id=run_id, payload=payload)
85
89
  return {
86
90
  "status": "ok",
87
91
  "response": result
@@ -111,6 +111,7 @@ class ArgusRelease(Model):
111
111
  pretty_name = columns.Text()
112
112
  description = columns.Text()
113
113
  github_repo_url = columns.Text()
114
+ valid_version_regex = columns.Text()
114
115
  assignee = columns.List(value_type=columns.UUID)
115
116
  picture_id = columns.UUID()
116
117
  enabled = columns.Boolean(default=lambda: True)
@@ -38,6 +38,7 @@ class PluginModelBase(Model):
38
38
  heartbeat = columns.Integer(default=lambda: int(time()))
39
39
  end_time = columns.DateTime(default=lambda: datetime.utcfromtimestamp(0))
40
40
  build_job_url = columns.Text()
41
+ product_version = columns.Text(index=True)
41
42
 
42
43
  # Test Logs Collection
43
44
  logs = columns.List(value_type=columns.Tuple(columns.Text(), columns.Text()))
@@ -180,10 +181,13 @@ class PluginModelBase(Model):
180
181
  def submit_product_version(self, version: str):
181
182
  raise NotImplementedError()
182
183
 
184
+ def set_product_version(self, version: str):
185
+ self.product_version = version
186
+
183
187
  def submit_logs(self, logs: list[dict]):
184
188
  raise NotImplementedError()
185
189
 
186
- def finish_run(self):
190
+ def finish_run(self, payload: dict = None):
187
191
  raise NotImplementedError()
188
192
 
189
193
 
@@ -0,0 +1,24 @@
1
+ from flask import Blueprint, request
2
+
3
+ from argus.backend.error_handlers import handle_api_exception
4
+ from argus.backend.service.user import api_login_required
5
+ from argus.backend.plugins.driver_matrix_tests.service import DriverMatrixService
6
+
7
+ bp = Blueprint("driver_matrix_api", __name__, url_prefix="/driver_matrix")
8
+ bp.register_error_handler(Exception, handle_api_exception)
9
+
10
+
11
+ @bp.route("/test_report", methods=["GET"])
12
+ @api_login_required
13
+ def driver_matrix_test_report():
14
+
15
+ build_id = request.args.get("buildId")
16
+ if not build_id:
17
+ raise Exception("No build id provided")
18
+
19
+
20
+ result = DriverMatrixService().tested_versions_report(build_id=build_id)
21
+ return {
22
+ "status": "ok",
23
+ "response": result
24
+ }
@@ -166,7 +166,7 @@ class DriverTestRun(PluginModelBase):
166
166
  def submit_product_version(self, version: str):
167
167
  self.scylla_version = version
168
168
 
169
- def finish_run(self):
169
+ def finish_run(self, payload: dict = None):
170
170
  self.end_time = datetime.utcnow()
171
171
 
172
172
  def submit_logs(self, logs: list[dict]):
@@ -2,6 +2,7 @@ from flask import Blueprint
2
2
 
3
3
  from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
4
  from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
5
+ from argus.backend.plugins.driver_matrix_tests.controller import bp as driver_matrix_api_bp
5
6
  from argus.backend.plugins.driver_matrix_tests.udt import TestCollection, EnvironmentInfo, TestCase, TestSuite
6
7
 
7
8
 
@@ -9,7 +10,7 @@ class PluginInfo(PluginInfoBase):
9
10
  # pylint: disable=too-few-public-methods
10
11
  name: str = "driver-matrix-tests"
11
12
  model: PluginModelBase = DriverTestRun
12
- controller: Blueprint = None
13
+ controller: Blueprint = driver_matrix_api_bp
13
14
  all_models = [
14
15
  DriverTestRun,
15
16
  ]
@@ -0,0 +1,42 @@
1
+ from argus.backend.db import ScyllaCluster
2
+ from argus.backend.models.web import ArgusRelease, ArgusTest
3
+ from argus.backend.plugins.driver_matrix_tests.model import DriverTestRun
4
+
5
+
6
+ class DriverMatrixService:
7
+ def tested_versions_report(self, build_id: str) -> dict:
8
+ db = ScyllaCluster.get()
9
+ all_runs_for_test_query = db.prepare(f"SELECT * FROM {DriverTestRun.table_name()} WHERE build_id = ?")
10
+
11
+ rows = list(db.session.execute(all_runs_for_test_query, parameters=(build_id,)).all())
12
+
13
+ if len(rows) == 0:
14
+ raise Exception(f"No results for build_id {build_id}", build_id)
15
+
16
+ latest = rows[0]
17
+ try:
18
+ test: ArgusTest = ArgusTest.get(id=latest["test_id"])
19
+ release: ArgusRelease = ArgusRelease.get(id=latest["release_id"])
20
+ except (ArgusTest.DoesNotExist, ArgusRelease.DoesNotExist):
21
+ raise Exception(f"Unable to find release and test information for build_id {build_id} and run_id {latest['id']}", build_id, latest["id"])
22
+
23
+
24
+ version_map = {}
25
+
26
+ for row in rows:
27
+ driver_versions = [(col["name"], col["driver"]) for col in row["test_collection"]]
28
+ for version, driver_type in driver_versions:
29
+ driver_type = driver_type if driver_type else "Unknown"
30
+ versions: list = version_map.get(driver_type, [])
31
+ if version not in versions:
32
+ versions.append(version)
33
+ versions.sort()
34
+ version_map[driver_type] = versions
35
+
36
+ response = {
37
+ "release": release.name,
38
+ "test": test.name,
39
+ "build_id": build_id,
40
+ "versions": version_map,
41
+ }
42
+ return response
@@ -0,0 +1,76 @@
1
+ from datetime import datetime
2
+ from uuid import UUID
3
+ from cassandra.cqlengine import columns
4
+ from cassandra.cqlengine.models import Model
5
+ from argus.backend.db import ScyllaCluster
6
+ from argus.backend.models.web import ArgusRelease
7
+ from argus.backend.plugins.core import PluginModelBase
8
+ from argus.backend.plugins.generic.types import GenericRunFinishRequest, GenericRunSubmitRequest
9
+ from argus.backend.util.enums import TestStatus
10
+
11
+
12
+ class GenericPluginException(Exception):
13
+ pass
14
+
15
+
16
+ class GenericRun(PluginModelBase):
17
+ _plugin_name = "generic"
18
+ __table_name__ = "generic_run"
19
+ logs = columns.Map(key_type=columns.Text(), value_type=columns.Text())
20
+ started_by = columns.Text()
21
+ # TODO: Legacy field name, should be renamed to product_version and abstracted
22
+ scylla_version = columns.Text()
23
+
24
+ @classmethod
25
+ def _stats_query(cls) -> str:
26
+ return ("SELECT id, test_id, group_id, release_id, status, start_time, build_job_url, build_id, "
27
+ f"assignee, end_time, investigation_status, heartbeat, scylla_version FROM {cls.table_name()} WHERE release_id = ?")
28
+
29
+ @classmethod
30
+ def get_distinct_product_versions(cls, release: ArgusRelease, cluster: ScyllaCluster = None) -> list[str]:
31
+ if not cluster:
32
+ cluster = ScyllaCluster.get()
33
+ statement = cluster.prepare(f"SELECT scylla_version FROM {cls.table_name()} WHERE release_id = ?")
34
+ rows = cluster.session.execute(query=statement, parameters=(release.id,))
35
+ unique_versions = {r["scylla_version"] for r in rows if r["scylla_version"]}
36
+
37
+ return sorted(list(unique_versions), reverse=True)
38
+
39
+ def submit_product_version(self, version: str):
40
+ self.scylla_version = version
41
+ self.set_product_version(version)
42
+
43
+ @classmethod
44
+ def load_test_run(cls, run_id: UUID) -> 'GenericRun':
45
+ return cls.get(id=run_id)
46
+
47
+ @classmethod
48
+ def submit_run(cls, request_data: GenericRunSubmitRequest) -> 'GenericRun':
49
+ try:
50
+ run = cls.get(id=request_data["run_id"])
51
+ raise GenericPluginException(f"Run with UUID {request_data['run_id']} already exists.", request_data["run_id"])
52
+ except cls.DoesNotExist:
53
+ pass
54
+ run = cls()
55
+ run.start_time = datetime.utcnow()
56
+ run.build_id = request_data["build_id"]
57
+ run.started_by = request_data["started_by"]
58
+ run.id = request_data["run_id"]
59
+ run.build_job_url = request_data["build_url"]
60
+ run.assign_categories()
61
+ try:
62
+ run.assignee = run.get_scheduled_assignee()
63
+ except Model.DoesNotExist:
64
+ run.assignee = None
65
+ if version := request_data.get("scylla_version"):
66
+ run.submit_product_version(version)
67
+ run.status = TestStatus.RUNNING.value
68
+ run.save()
69
+
70
+ return run
71
+
72
+ def finish_run(self, payload: GenericRunFinishRequest = None):
73
+ self.end_time = datetime.utcnow()
74
+ self.status = TestStatus(payload["status"]).value
75
+ if version := payload.get("scylla_version"):
76
+ self.submit_product_version(version)
@@ -0,0 +1,16 @@
1
+ from flask import Blueprint
2
+
3
+ from argus.backend.plugins.core import PluginInfoBase, PluginModelBase
4
+ from argus.backend.plugins.generic.model import GenericRun
5
+
6
+
7
+ class PluginInfo(PluginInfoBase):
8
+ # pylint: disable=too-few-public-methods
9
+ name: str = "generic"
10
+ model: PluginModelBase = GenericRun
11
+ controller: Blueprint = None
12
+ all_models = [
13
+ GenericRun
14
+ ]
15
+ all_types = [
16
+ ]
@@ -0,0 +1,13 @@
1
+ from typing import TypedDict
2
+
3
+ class GenericRunSubmitRequest(TypedDict):
4
+ build_id: str
5
+ build_url: str
6
+ run_id: str
7
+ started_by: str
8
+ scylla_version: str | None
9
+
10
+
11
+ class GenericRunFinishRequest(TypedDict):
12
+ status: str
13
+ scylla_version: str | None
@@ -383,7 +383,7 @@ class SCTService:
383
383
  "aborted": 0,
384
384
  }
385
385
  )
386
- if run["status"] in ["passed", "failed", "aborted"]:
386
+ if run["status"] in ["passed", "failed", "aborted", "test_error"]:
387
387
  metadata[run["status"]] += 1
388
388
  kernel_metadata[kernel_package["version"]] = metadata
389
389
 
@@ -47,9 +47,9 @@ class SCTTestRunSubmissionRequest():
47
47
  job_url: str
48
48
  started_by: str
49
49
  commit_id: str
50
- origin_url: str | None
51
- branch_name: str | None
52
50
  sct_config: dict | None
51
+ origin_url: Optional[str] = field(default=None)
52
+ branch_name: Optional[str] = field(default=None)
53
53
  runner_public_ip: Optional[str] = field(default=None)
54
54
  runner_private_ip: Optional[str] = field(default=None)
55
55
 
@@ -60,6 +60,7 @@ class SCTTestRun(PluginModelBase):
60
60
 
61
61
  # Test Details
62
62
  test_name = columns.Text()
63
+ stress_duration = columns.Float()
63
64
  scm_revision_id = columns.Text()
64
65
  branch_name = columns.Text()
65
66
  origin_url = columns.Text()
@@ -181,6 +182,8 @@ class SCTTestRun(PluginModelBase):
181
182
 
182
183
  if req.sct_config:
183
184
  backend = req.sct_config.get("cluster_backend")
185
+ if duration_override := req.sct_config.get("stress_duration"):
186
+ run.stress_duration = float(duration_override)
184
187
  region_key = SCT_REGION_PROPERTY_MAP.get(backend, SCT_REGION_PROPERTY_MAP["default"])
185
188
  raw_regions = req.sct_config.get(region_key) or "undefined_region"
186
189
  regions = raw_regions.split() if isinstance(raw_regions, str) else raw_regions
@@ -212,7 +215,7 @@ class SCTTestRun(PluginModelBase):
212
215
  def submit_product_version(self, version: str):
213
216
  self.scylla_version = version
214
217
 
215
- def finish_run(self):
218
+ def finish_run(self, payload: dict = None):
216
219
  self.end_time = datetime.utcnow()
217
220
 
218
221
  def submit_logs(self, logs: list[dict]):
@@ -20,6 +20,8 @@ class CloudInstanceDetails(UserType):
20
20
  region = columns.Text()
21
21
  public_ip = columns.Text()
22
22
  private_ip = columns.Text()
23
+ dc_name = columns.Text()
24
+ rack_name = columns.Text()
23
25
  creation_time = columns.Integer(default=lambda: int(time()))
24
26
  termination_time = columns.Integer(default=lambda: 0)
25
27
  termination_reason = columns.Text(default=lambda: "")
@@ -65,7 +65,7 @@ class SirenadaRun(PluginModelBase):
65
65
  def submit_logs(self, logs: dict[str, str]):
66
66
  raise SirenadaPluginException("Log submission is not supported for Sirenada")
67
67
 
68
- def finish_run(self):
68
+ def finish_run(self, payload: dict = None):
69
69
  raise SirenadaPluginException("Sirenada runs do not need finalization")
70
70
 
71
71
  @classmethod
@@ -62,10 +62,10 @@ class ClientService:
62
62
 
63
63
  return "Submitted"
64
64
 
65
- def finish_run(self, run_type: str, run_id: str) -> str:
65
+ def finish_run(self, run_type: str, run_id: str, payload: dict | None = None) -> str:
66
66
  model = self.get_model(run_type)
67
67
  run = model.load_test_run(UUID(run_id))
68
- run.finish_run()
68
+ run.finish_run(payload)
69
69
  run.save()
70
70
 
71
71
  return "Finalized"
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import TypedDict
2
3
  from uuid import UUID
3
4
  from argus.backend.db import ScyllaCluster
4
5
  from argus.backend.plugins.sct.testrun import SCTTestRun
@@ -11,6 +12,16 @@ class ReleaseManagerException(Exception):
11
12
  pass
12
13
 
13
14
 
15
+ class ReleaseEditPayload(TypedDict):
16
+ id: str
17
+ pretty_name: str
18
+ description: str
19
+ valid_version_regex: str | None
20
+ enabled: bool
21
+ perpetual: bool
22
+ dormant: bool
23
+
24
+
14
25
  class ReleaseManagerService:
15
26
  def __init__(self) -> None:
16
27
  self.session = ScyllaCluster.get_session()
@@ -156,6 +167,33 @@ class ReleaseManagerService:
156
167
 
157
168
  return True
158
169
 
170
+ def edit_release(self, payload: ReleaseEditPayload) -> bool:
171
+
172
+ release: ArgusRelease = ArgusRelease.get(id=payload["id"])
173
+ release.pretty_name = payload["pretty_name"]
174
+ release.perpetual = payload["perpetual"]
175
+ release.enabled = payload["enabled"]
176
+ release.dormant = payload["dormant"]
177
+ release.description = payload["description"]
178
+ release.valid_version_regex = payload["valid_version_regex"]
179
+
180
+
181
+ release.save()
182
+ return True
183
+
184
+ def delete_release(self, release_id: str) -> bool:
185
+
186
+ release: ArgusRelease = ArgusRelease.get(id=release_id)
187
+
188
+ release_groups = ArgusGroup.filter(release_id=release.id)
189
+ release_tests = ArgusTest.filter(release_id=release.id)
190
+
191
+ for entity in [*release_groups.all(), *release_tests.all()]:
192
+ entity.delete()
193
+
194
+ release.delete()
195
+ return True
196
+
159
197
  def batch_move_tests(self, new_group_id: str, tests: list[str]) -> bool:
160
198
  group = ArgusGroup.get(id=UUID(new_group_id))
161
199
 
@@ -33,6 +33,7 @@ class TestRunStatRow(TypedDict):
33
33
  class ComparableTestStatus:
34
34
  PRIORITY_MAP = {
35
35
  TestStatus.FAILED: 10,
36
+ TestStatus.TEST_ERROR: 10,
36
37
  TestStatus.ABORTED: 9,
37
38
  TestStatus.RUNNING: 8,
38
39
  TestStatus.CREATED: 7,
@@ -126,7 +127,7 @@ def generate_field_status_map(
126
127
  field_name = "status",
127
128
  container_class = TestStatus,
128
129
  cmp_class = ComparableTestStatus
129
- ) -> dict[int, str]:
130
+ ) -> dict[int, tuple[str, TestRunStatRow]]:
130
131
 
131
132
  status_map = {}
132
133
  for run in last_runs:
@@ -136,7 +137,7 @@ def generate_field_status_map(
136
137
  if cmp_class(container_class(status)) < cmp_class(container_class(run[field_name])):
137
138
  status_map[run_number] = run[field_name]
138
139
  case _:
139
- status_map[run_number] = run[field_name]
140
+ status_map[run_number] = (run[field_name], run)
140
141
  return status_map
141
142
 
142
143
 
@@ -279,6 +280,7 @@ class TestStats:
279
280
  self.has_comments = False
280
281
  self.schedules = schedules if schedules else tuple()
281
282
  self.is_scheduled = len(self.schedules) > 0
283
+ self.tracked_run_number = None
282
284
 
283
285
  def to_dict(self) -> dict:
284
286
  return {
@@ -288,7 +290,8 @@ class TestStats:
288
290
  "last_runs": self.last_runs,
289
291
  "start_time": self.start_time,
290
292
  "hasBugReport": self.has_bug_report,
291
- "hasComments": self.has_comments
293
+ "hasComments": self.has_comments,
294
+ "buildNumber": self.tracked_run_number,
292
295
  }
293
296
 
294
297
  def collect(self, limited=False):
@@ -305,11 +308,10 @@ class TestStats:
305
308
  self.parent_group.increment_status(status=self.status)
306
309
  return
307
310
  status_map = generate_field_status_map(last_runs)
308
- investigation_status_map = generate_field_status_map(
309
- last_runs, "investigation_status", TestInvestigationStatus, ComparableTestInvestigationStatus)
310
311
 
311
- self.status = status_map.get(get_build_number(last_run["build_job_url"]))
312
- self.investigation_status = investigation_status_map.get(get_build_number(last_run["build_job_url"]))
312
+ worst_case = status_map.get(get_build_number(last_run["build_job_url"]))
313
+ self.status = worst_case[0]
314
+ self.investigation_status = worst_case[1]["investigation_status"]
313
315
  self.start_time = last_run["start_time"]
314
316
 
315
317
  self.parent_group.increment_status(status=self.status)
@@ -318,6 +320,7 @@ class TestStats:
318
320
 
319
321
  self.last_runs = [
320
322
  {
323
+ "id": run["id"],
321
324
  "status": run["status"],
322
325
  "build_number": get_build_number(run["build_job_url"]),
323
326
  "build_job_name": run["build_id"],
@@ -327,10 +330,18 @@ class TestStats:
327
330
  "comments": [dict(i.items()) for i in self.parent_group.parent_release.comments if i.test_run_id == run["id"]],
328
331
  }
329
332
  for run in last_runs
330
- ][:5]
331
- self.has_bug_report = len(self.last_runs[0]["issues"]) > 0
333
+ ]
334
+ try:
335
+ target_run = next(run for run in self.last_runs if run["id"] == worst_case[1]["id"])
336
+ except StopIteration:
337
+ target_run = worst_case[1]
338
+ target_run["issues"] = [dict(i.items()) for i in self.parent_group.parent_release.issues if i.run_id == target_run["id"]]
339
+ target_run["comments"] = [dict(i.items()) for i in self.parent_group.parent_release.comments if i.test_run_id == target_run["id"]]
340
+ self.has_bug_report = len(target_run["issues"]) > 0
332
341
  self.parent_group.parent_release.has_bug_report = self.has_bug_report or self.parent_group.parent_release.has_bug_report
333
- self.has_comments = len(self.last_runs[0]["comments"]) > 0
342
+ self.has_comments = len(target_run["comments"]) > 0
343
+ self.last_runs = self.last_runs[:5]
344
+ self.tracked_run_number = target_run.get("build_number", get_build_number(target_run.get("build_job_url")))
334
345
 
335
346
 
336
347
  class ReleaseStatsCollector:
@@ -343,7 +354,7 @@ class ReleaseStatsCollector:
343
354
  self.release_name = release_name
344
355
  self.release_version = release_version
345
356
 
346
- def collect(self, limited=False, force=False) -> dict:
357
+ def collect(self, limited=False, force=False, include_no_version=False) -> dict:
347
358
  self.release: ArgusRelease = ArgusRelease.get(name=self.release_name)
348
359
  self.release_rows = [row for plugin in all_plugin_models()
349
360
  for row in plugin.get_stats_for_release(release=self.release)]
@@ -353,8 +364,18 @@ class ReleaseStatsCollector:
353
364
  }
354
365
 
355
366
  if self.release_version:
356
- self.release_rows = list(
357
- filter(lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"], self.release_rows))
367
+ if include_no_version:
368
+ expr = lambda row: row["scylla_version"] == self.release_version or not row["scylla_version"]
369
+ elif self.release_version == "!noVersion":
370
+ expr = lambda row: not row["scylla_version"]
371
+ else:
372
+ expr = lambda row: row["scylla_version"] == self.release_version
373
+ else:
374
+ if include_no_version:
375
+ expr = lambda row: row
376
+ else:
377
+ expr = lambda row: row["scylla_version"]
378
+ self.release_rows = list(filter(expr, self.release_rows))
358
379
 
359
380
  self.release_stats = ReleaseStats(release=self.release)
360
381
  self.release_stats.collect(rows=self.release_rows, limited=limited, force=force)
@@ -341,7 +341,7 @@ class TestRunService:
341
341
  }
342
342
  )
343
343
  if issue_request.status_code != 200:
344
- raise Exception(f"Error getting issue state: Response: HTTP {issue_request.status_code}", issue_request)
344
+ raise Exception(f"Error getting issue state: Response: HTTP {issue_request.status_code}", issue_request.json())
345
345
 
346
346
  issue_state: dict[str, Any] = issue_request.json()
347
347
 
@@ -14,6 +14,7 @@ class TestStatus(str, Enum):
14
14
  CREATED = "created"
15
15
  RUNNING = "running"
16
16
  FAILED = "failed"
17
+ TEST_ERROR = "test_error"
17
18
  ERROR = "error"
18
19
  PASSED = "passed"
19
20
  ABORTED = "aborted"
argus/client/base.py CHANGED
@@ -170,12 +170,14 @@ class ArgusClient:
170
170
  }
171
171
  )
172
172
 
173
- def finalize_run(self, run_type: str, run_id: UUID) -> requests.Response:
173
+ def finalize_run(self, run_type: str, run_id: UUID, body: dict = None) -> requests.Response:
174
+ body = body if body else {}
174
175
  return self.post(
175
176
  endpoint=self.Routes.FINALIZE,
176
177
  location_params={"type": run_type, "id": str(run_id)},
177
178
  body={
178
179
  **self.generic_body,
180
+ **body,
179
181
  }
180
182
  )
181
183
 
@@ -0,0 +1,47 @@
1
+ import click
2
+ import logging
3
+ from argus.backend.util.enums import TestStatus
4
+
5
+ from argus.client.generic.client import ArgusGenericClient
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ @click.group
11
+ def cli():
12
+ pass
13
+
14
+
15
+ @click.command("submit")
16
+ @click.option("--api-key", help="Argus API key for authorization", required=True)
17
+ @click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
18
+ @click.option("--id", required=True, help="UUID (v4 or v1) unique to the job")
19
+ @click.option("--build-id", required=True, help="Unique job identifier in the build system, e.g. scylla-master/group/job for jenkins (The full path)")
20
+ @click.option("--build-url", required=True, help="Job URL in the build system")
21
+ @click.option("--started-by", required=True, help="Username of the user who started the job")
22
+ @click.option("--scylla-version", required=False, default=None, help="Version of Scylla used for this job")
23
+ def submit_run(api_key: str, base_url: str, id: str, build_id: str, build_url: str, started_by: str, scylla_version: str = None):
24
+ LOGGER.info("Submitting %s (%s) to Argus...", build_id, id)
25
+ client = ArgusGenericClient(auth_token=api_key, base_url=base_url)
26
+ client.submit_generic_run(build_id=build_id, run_id=id, started_by=started_by, build_url=build_url, scylla_version=scylla_version)
27
+ LOGGER.info("Done.")
28
+
29
+
30
+ @click.command("finish")
31
+ @click.option("--api-key", help="Argus API key for authorization", required=True)
32
+ @click.option("--base-url", default="https://argus.scylladb.com", help="Base URL for argus instance")
33
+ @click.option("--id", required=True, help="UUID (v4 or v1) unique to the job")
34
+ @click.option("--status", required=True, help="Resulting job status")
35
+ @click.option("--scylla-version", required=False, default=None, help="Version of Scylla used for this job")
36
+ def finish_run(api_key: str, base_url: str, id: str, status: str, scylla_version: str = None):
37
+ client = ArgusGenericClient(auth_token=api_key, base_url=base_url)
38
+ status = TestStatus(status)
39
+ client.finalize_generic_run(run_id=id, status=status, scylla_version=scylla_version)
40
+
41
+
42
+ cli.add_command(submit_run)
43
+ cli.add_command(finish_run)
44
+
45
+
46
+ if __name__ == "__main__":
47
+ cli()
@@ -0,0 +1,31 @@
1
+ import logging
2
+ from argus.client.base import ArgusClient
3
+
4
+
5
+ LOGGER = logging.getLogger(__name__)
6
+
7
+ class ArgusGenericClient(ArgusClient):
8
+ test_type = "generic"
9
+ schema_version: None = "v1"
10
+ def __init__(self, auth_token: str, base_url: str, api_version="v1") -> None:
11
+ super().__init__(auth_token, base_url, api_version)
12
+
13
+ def submit_generic_run(self, build_id: str, run_id: str, started_by: str, build_url: str, scylla_version: str | None = None):
14
+ request_body = {
15
+ "build_id": build_id,
16
+ "run_id": run_id,
17
+ "started_by": started_by,
18
+ "build_url": build_url,
19
+ "scylla_version": scylla_version
20
+ }
21
+
22
+ response = self.submit_run(run_type=self.test_type, run_body=request_body)
23
+ self.check_response(response)
24
+
25
+
26
+ def finalize_generic_run(self, run_id: str, status: str, scylla_version: str | None = None):
27
+ response = self.finalize_run(run_type=self.test_type, run_id=run_id, body={
28
+ "status": status,
29
+ "scylla_version": scylla_version,
30
+ })
31
+ self.check_response(response)
@@ -148,7 +148,7 @@ class ArgusSCTClient(ArgusClient):
148
148
  self.check_response(response)
149
149
 
150
150
  def create_resource(self, name: str, resource_type: str, public_ip: str, private_ip: str,
151
- region: str, provider: str, shards_amount: int, state=ResourceState.RUNNING) -> None:
151
+ region: str, provider: str, dc_name: str, rack_name: str, shards_amount: int, state=ResourceState.RUNNING) -> None:
152
152
  """
153
153
  Creates a cloud resource record in argus.
154
154
  """
@@ -164,6 +164,8 @@ class ArgusSCTClient(ArgusClient):
164
164
  "instance_details": {
165
165
  "provider": provider,
166
166
  "region": region,
167
+ "dc_name": dc_name,
168
+ "rack_name": rack_name,
167
169
  "public_ip": public_ip,
168
170
  "private_ip": private_ip,
169
171
  "shards_amount": shards_amount,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: argus-alm
3
- Version: 0.11.7
3
+ Version: 0.12.1
4
4
  Summary: Argus
5
5
  Home-page: https://github.com/scylladb/argus
6
6
  License: Apache-2.0
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Dist: click (>=8.1.3,<9.0.0)
14
15
  Requires-Dist: requests (>=2.26.0,<3.0.0)
15
16
  Project-URL: Repository, https://github.com/scylladb/argus
16
17
  Description-Content-Type: text/markdown
@@ -4,10 +4,10 @@ argus/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  argus/backend/cli.py,sha256=EZFI70CIaHNt93ARvEkNanbC0_nB7loIPq9giEQ3P2s,1342
5
5
  argus/backend/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  argus/backend/controller/admin.py,sha256=2z29RX7ZQO_VTklSKH9RrEj-Ag2SsvyOaIzWDKr0ahQ,575
7
- argus/backend/controller/admin_api.py,sha256=6-BSQpeaMIq1WNAJX_rIe5x3-aslDDfolzhKHOu3yvM,5545
8
- argus/backend/controller/api.py,sha256=hnIarcxOyujdy7FcuH68gWvh-6AtmjImLi3xgF088eQ,13332
7
+ argus/backend/controller/admin_api.py,sha256=9CkewYmnqKJpSKoXyKDMxuMziyAWQ1lRD3dMSh5R3PI,6249
8
+ argus/backend/controller/api.py,sha256=_hLJcO__jFMB4kFonkwVgUkYdLLQTSvCrCLQKKrvgr8,13450
9
9
  argus/backend/controller/auth.py,sha256=nwF_5uZLxPOCLm2ljLyw2OxGur6fmPM7WAAoGJLI_kk,2030
10
- argus/backend/controller/client_api.py,sha256=zixOLNTAZ8_dWWPXCdGPiJHbJPffr7t-tbh8ZDkoSzI,2897
10
+ argus/backend/controller/client_api.py,sha256=SM6HRlnoguxkPrqHzm42b5JsoQ2b8-1hTtfLWe832hc,3007
11
11
  argus/backend/controller/main.py,sha256=XQLwGPXm0MlDIq-Bb84xYiiksmbE0oSjDAwMdDd14BE,8296
12
12
  argus/backend/controller/notification_api.py,sha256=wz7V4nE6Mxclpq78P8gNnCyeQ7xA9BBJjZ-dPhLLd2I,1964
13
13
  argus/backend/controller/notifications.py,sha256=zMSJln72BGU6Q_nQvJesMnuvJ57Ucbov4M2ZI-37Bxo,290
@@ -18,48 +18,54 @@ argus/backend/db.py,sha256=bBiraYD05Qex28yZHjSP1bRlcMsc6oTYGt792zXmaHo,4101
18
18
  argus/backend/error_handlers.py,sha256=IEjz7Vzfldv1PTOeHrpRWmRsgBrHtAW0PXHUJZDovAE,480
19
19
  argus/backend/events/event_processors.py,sha256=bsmBayiXvlGn3aqiT2z9WgwnVBRtn2cRqkgn4pLodck,1291
20
20
  argus/backend/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- argus/backend/models/web.py,sha256=FHTWehdnlOPVX_2jXo0aaA68ruuXr6UHt3yKNB1Ch2Y,12165
21
+ argus/backend/models/web.py,sha256=dUWQQaw5TivSCOuGQqAxIH9yVraGXX7sayNBloIS3n8,12206
22
22
  argus/backend/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- argus/backend/plugins/core.py,sha256=z-p5Cj-dci4mYQ6PtEwCQXEhvubDfXvJEcqC6nYMonM,7446
24
- argus/backend/plugins/driver_matrix_tests/controller.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- argus/backend/plugins/driver_matrix_tests/model.py,sha256=-DTr-e9LiLaZ5Ra4k00-QmGDJIIKSt4dI0SYnZQJqwc,6800
26
- argus/backend/plugins/driver_matrix_tests/plugin.py,sha256=NZN6lpHFpNuBkv2D_gGHh03402z0qlOeCWjgZXRs2c4,637
23
+ argus/backend/plugins/core.py,sha256=Tit-JlybrFCSsqoKjp_BFmtaxRk_AiocGOKqMfBovj4,7604
24
+ argus/backend/plugins/driver_matrix_tests/controller.py,sha256=9Q6QCripzM528SGsQnYupM6k7HW7hUzkNyjJFiXXfEw,739
25
+ argus/backend/plugins/driver_matrix_tests/model.py,sha256=Ay_Q6EAF4CwAW0fejdia4zyKcZOc943eZWT5nWIefYM,6822
26
+ argus/backend/plugins/driver_matrix_tests/plugin.py,sha256=72ESU7s8C6ovVMfJTlYwtaokdvRp_HJF1_czm1UMhKg,745
27
27
  argus/backend/plugins/driver_matrix_tests/raw_types.py,sha256=A108HCnv5q0RHfNRhUJrTpRy3fG7sPxr9Sk4gfsbooU,600
28
+ argus/backend/plugins/driver_matrix_tests/service.py,sha256=dxb8VGTJLIyVqVrZ4RtzCXgnmS2qg2RAGhcp_SARt9I,1737
28
29
  argus/backend/plugins/driver_matrix_tests/udt.py,sha256=6lydzF3AQHm3GR5bKEDu1xLPYsLaBL3o-wu9NpabbqA,1134
30
+ argus/backend/plugins/generic/model.py,sha256=y5-eJRxMm8L2zDRVXcG-ZK7mrslZ5rGPTv-ihXFt9Yc,3096
31
+ argus/backend/plugins/generic/plugin.py,sha256=5URbQVUCizrk-KZqb6I0P_8nLUekjYh-Js7ZLKVoBAA,407
32
+ argus/backend/plugins/generic/types.py,sha256=jlZUcQ7r153ziyl3ZJmix7AzL2G1aX9N_z-4Kw9trWc,267
29
33
  argus/backend/plugins/loader.py,sha256=0MmGNBVle51tIyH7lA7Lz2L8PcGKGub9vyFuwnLm840,1320
30
34
  argus/backend/plugins/sct/controller.py,sha256=ek4gNiraDzd81QMpHKCzpugUSkwbXCHKAPsjD9ol6CE,4791
31
35
  argus/backend/plugins/sct/plugin.py,sha256=3TlX5NAd7bv4aMI9FJNJLkf8o2bcahZhZobAV4xAbzU,965
32
36
  argus/backend/plugins/sct/resource_setup.py,sha256=hwfAOu-oKOH42tjtzJhiqwq_MtUE9_HevoFyql8JKqY,10120
33
- argus/backend/plugins/sct/service.py,sha256=VR9fEwc43Y9nFW9__Lvbm613VIb1lp5Y3P3dXm-LwRk,17618
34
- argus/backend/plugins/sct/testrun.py,sha256=sZW7ZzjX4I2ni1tTKt68T0Kp6x0IwaJsAThgNwNchxU,9168
37
+ argus/backend/plugins/sct/service.py,sha256=fiwnIE4guVOnw5c8g8xwOSr5BoTI_AoyVzJkWYhnQ5A,17632
38
+ argus/backend/plugins/sct/testrun.py,sha256=qU-81IRs7UYTs5ozcDlATLZWGUp6I8X3XOIVJoz9RFc,9416
35
39
  argus/backend/plugins/sct/types.py,sha256=avLL3fzug_n6GbtlW8bcPewcDGi1DxHFD0vp-JZ7hdQ,969
36
- argus/backend/plugins/sct/udt.py,sha256=KVVkzaiEeyKs2ntFjXQ6BpoBjX4kLuSK-csgIVyNK00,2819
37
- argus/backend/plugins/sirenada/model.py,sha256=npKs6GurFXgddOVEiD5hmPwCPi7RPAi9HFs-3s99C8w,4661
40
+ argus/backend/plugins/sct/udt.py,sha256=oP2xzUGy2o4fVICcZyGyvLWapR2Rox-Myp_FTXHmxlQ,2879
41
+ argus/backend/plugins/sirenada/model.py,sha256=ku9jITNf9wU0YZJ9JwkopJYCC2MKek1M1Q7aPKZzt5M,4683
38
42
  argus/backend/plugins/sirenada/plugin.py,sha256=AlQAakwy3u-OqAqqK3RonUR5oDm-JoiwBUDUF3YEVP4,447
39
43
  argus/backend/plugins/sirenada/types.py,sha256=Gm3XMK9YJoozVaeM9XE7n8iRxA6PKBrS23Mo2vJfdLs,697
40
44
  argus/backend/service/admin.py,sha256=_VnWl3CkZBOAie_pPbd9sbXZUpBf2SApyNoFZLfB_QI,637
41
45
  argus/backend/service/argus_service.py,sha256=o415BalaQ-FP5CDSjeypiXJ4ejaVVXqive25syPhRdM,24192
42
46
  argus/backend/service/build_system_monitor.py,sha256=_Bxugk4CCh2z-I-i74QMPe7M8j0dmBMDri2dd3WGVew,7328
43
- argus/backend/service/client_service.py,sha256=_eH-sq8507oKXXtMFDAg2kUc445qQucr2fC6pKbmjZc,2290
47
+ argus/backend/service/client_service.py,sha256=CS5esppd9s-SgUYE-HVLkfz-MrN8zxPouf9e4VlPV_M,2326
44
48
  argus/backend/service/event_service.py,sha256=iYeqxN2QCYTjYB1WPPv4BEFLXG0Oz3TvskkaK4v9pVY,654
45
49
  argus/backend/service/notification_manager.py,sha256=h00Ej_-hH9H7pq0wah_1TH8dnpPyPNsgVJNO1rwJi7o,7011
46
- argus/backend/service/release_manager.py,sha256=v2yg45oWBpK_uwqkHtJHPTPbe21_cJu3skmzeaChVhw,6259
47
- argus/backend/service/stats.py,sha256=FjVmpHTNwRFOC79pzu8gz49yM8lUwcroTfjzCq0laiY,14102
50
+ argus/backend/service/release_manager.py,sha256=DymV5OI53ClRdc4p7jlCGT6xPqrjIxHjs8EDiyWffqQ,7362
51
+ argus/backend/service/stats.py,sha256=4WEtd5gdzT-dzOjD1NUHdBfVurxnwXcawjseu-kqxHw,15167
48
52
  argus/backend/service/team_manager_service.py,sha256=zY5dvy3ffvQbJuXBvlWKE5dS5LQ3ss6tkFE-cwFZsdw,3010
49
- argus/backend/service/testrun.py,sha256=p0vRGkQYEsDxWzi0JLH-l8ijDyDcmMNeallMk5VrOds,19699
53
+ argus/backend/service/testrun.py,sha256=ggjTcWjyvsPXSSv_v80rrp3bHaqCOjcxYEewXrW9AXg,19706
50
54
  argus/backend/service/user.py,sha256=N3t43rgKMnSsPXU5R9bigEEGbPjYrc6MsJtof3z7kDE,9027
51
55
  argus/backend/template_filters.py,sha256=hD8eDyBYp-X6JVoyQhM-TWYv3MuyorAv0Emz728iGcU,523
52
56
  argus/backend/util/common.py,sha256=fmE9cKnJ1CX_Cz4Mw1d_M-T-jwPh8DkKr_fLAx8LY9M,1458
53
57
  argus/backend/util/config.py,sha256=Sm0LCRkabYaSUkXNPglyjMr45GCDBNXqJLkmB_s51f0,860
54
58
  argus/backend/util/encoders.py,sha256=VxOnUanHP9FjcaobYupV-pZ3Udzrrrq7zZcbNpVXaKM,646
55
- argus/backend/util/enums.py,sha256=ASL-mqhEtjBKAB-5YDSvGXe7amKjiVv0Q-Gf6eJNbUM,719
59
+ argus/backend/util/enums.py,sha256=EhTQrgedlEz5sDYJ1gFnE2eC2nc1neQCRgzOgssQvWY,749
56
60
  argus/backend/util/logsetup.py,sha256=XWyFLC1_J5G8NcR7oC9l-Pf02ybAvEZR95y5LoY4W48,2438
57
61
  argus/backend/util/module_loaders.py,sha256=AcIlX-VRmUQ2THFKT8DLefLSE62Eub2hCxIosg3WgE0,698
58
62
  argus/backend/util/send_email.py,sha256=Bb2Hta7WlBCvDKga0_WPFWgxWJEfKpectOGypgf9xzo,3217
59
63
  argus/client/__init__.py,sha256=bO9_j5_jK5kvTHR46KEZ0Y-p0li7CBW8QSd-K5Ez4vA,42
60
- argus/client/base.py,sha256=WHYDUSFhhvAyzU3pKPohlsKifxwifx0vWA42VoAha9M,6676
64
+ argus/client/base.py,sha256=-R-BINTolY06lUQLOLGlsWzza4fBdtLBW-4V3NT64vg,6755
61
65
  argus/client/driver_matrix_tests/client.py,sha256=3LIS2hwZ3wVKtQDEMiQGBsIoIcpKi29chheF_yRM704,7800
62
- argus/client/sct/client.py,sha256=mZ2_kr95iS0F9rUTciEOZ_bNc4jlMKT4iapncR-wTxc,10082
66
+ argus/client/generic/cli.py,sha256=IJkgEZ5VOAeqp5SlLM13Y5m8e34Cqnyz8WkfeKoN7so,2208
67
+ argus/client/generic/client.py,sha256=l4PDjDy65Mm2OI9ZLSnyd8_2i4Ei1Pp9yRt3bRX8s2Y,1114
68
+ argus/client/sct/client.py,sha256=0xRIeYtjTFGfDS03klSTFie3uxrQqB51xkeOW223MiQ,10204
63
69
  argus/client/sct/types.py,sha256=VLgVe7qPmJtCLqtPnuX8N8kMKZq-iY3SKz68nvU6nJ4,371
64
70
  argus/client/sirenada/client.py,sha256=ilcyLXJb-0gKbmb9WSPr-Yvldh73joGBhRDoilQoSJ4,6220
65
71
  argus/db/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -70,7 +76,8 @@ argus/db/db_types.py,sha256=iLbmrUaDzrBw0kDCnvW0FSZ9-kNc3uQY-fsbIPymV4E,3612
70
76
  argus/db/interface.py,sha256=HroyA1Yijz5cXLdYbxorHCEu0GH9VeMMqB36IHTlcew,17146
71
77
  argus/db/testrun.py,sha256=0YG7FIH5FLQeNlYULxC6rhhyru2rziSMe3qKtYzTBnc,26014
72
78
  argus/db/utils.py,sha256=YAWsuLjUScSgKgdaL5aF4Sgr13gqH29Mb5cLctX4V_w,337
73
- argus_alm-0.11.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
74
- argus_alm-0.11.7.dist-info/METADATA,sha256=EB9KDY8eTj3-hjylCscgjBDi6gBI1WDeQC3uj0Jg8mg,6824
75
- argus_alm-0.11.7.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
76
- argus_alm-0.11.7.dist-info/RECORD,,
79
+ argus_alm-0.12.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
80
+ argus_alm-0.12.1.dist-info/METADATA,sha256=_jA8O6J6pa-brcL9f3uwicRdisjfvBwtT-fWXyLP9-c,6862
81
+ argus_alm-0.12.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
82
+ argus_alm-0.12.1.dist-info/entry_points.txt,sha256=zEqrAK95P8AAhKbwO4lgrQzKBWqCzHH9zlUPCaCVHoQ,69
83
+ argus_alm-0.12.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.4.0
2
+ Generator: poetry-core 1.6.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ argus-client-generic=argus.client.generic.cli:cli
3
+