argus-alm 0.12.9__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- argus/client/base.py +1 -1
- argus/client/driver_matrix_tests/cli.py +2 -2
- argus/client/driver_matrix_tests/client.py +1 -1
- argus/client/generic/cli.py +2 -2
- argus/client/generic_result.py +3 -2
- argus/client/sct/client.py +3 -3
- argus/client/sirenada/client.py +1 -1
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/METADATA +2 -4
- argus_alm-0.13.0.dist-info/RECORD +20 -0
- argus/backend/.gitkeep +0 -0
- argus/backend/cli.py +0 -41
- argus/backend/controller/__init__.py +0 -0
- argus/backend/controller/admin.py +0 -20
- argus/backend/controller/admin_api.py +0 -354
- argus/backend/controller/api.py +0 -529
- argus/backend/controller/auth.py +0 -67
- argus/backend/controller/client_api.py +0 -108
- argus/backend/controller/main.py +0 -274
- argus/backend/controller/notification_api.py +0 -72
- argus/backend/controller/notifications.py +0 -13
- argus/backend/controller/team.py +0 -126
- argus/backend/controller/team_ui.py +0 -18
- argus/backend/controller/testrun_api.py +0 -482
- argus/backend/controller/view_api.py +0 -162
- argus/backend/db.py +0 -100
- argus/backend/error_handlers.py +0 -21
- argus/backend/events/event_processors.py +0 -34
- argus/backend/models/__init__.py +0 -0
- argus/backend/models/result.py +0 -138
- argus/backend/models/web.py +0 -389
- argus/backend/plugins/__init__.py +0 -0
- argus/backend/plugins/core.py +0 -225
- argus/backend/plugins/driver_matrix_tests/controller.py +0 -63
- argus/backend/plugins/driver_matrix_tests/model.py +0 -421
- argus/backend/plugins/driver_matrix_tests/plugin.py +0 -22
- argus/backend/plugins/driver_matrix_tests/raw_types.py +0 -62
- argus/backend/plugins/driver_matrix_tests/service.py +0 -60
- argus/backend/plugins/driver_matrix_tests/udt.py +0 -42
- argus/backend/plugins/generic/model.py +0 -79
- argus/backend/plugins/generic/plugin.py +0 -16
- argus/backend/plugins/generic/types.py +0 -13
- argus/backend/plugins/loader.py +0 -40
- argus/backend/plugins/sct/controller.py +0 -185
- argus/backend/plugins/sct/plugin.py +0 -38
- argus/backend/plugins/sct/resource_setup.py +0 -178
- argus/backend/plugins/sct/service.py +0 -491
- argus/backend/plugins/sct/testrun.py +0 -272
- argus/backend/plugins/sct/udt.py +0 -101
- argus/backend/plugins/sirenada/model.py +0 -113
- argus/backend/plugins/sirenada/plugin.py +0 -17
- argus/backend/service/admin.py +0 -27
- argus/backend/service/argus_service.py +0 -688
- argus/backend/service/build_system_monitor.py +0 -188
- argus/backend/service/client_service.py +0 -122
- argus/backend/service/event_service.py +0 -18
- argus/backend/service/jenkins_service.py +0 -240
- argus/backend/service/notification_manager.py +0 -150
- argus/backend/service/release_manager.py +0 -230
- argus/backend/service/results_service.py +0 -317
- argus/backend/service/stats.py +0 -540
- argus/backend/service/team_manager_service.py +0 -83
- argus/backend/service/testrun.py +0 -559
- argus/backend/service/user.py +0 -307
- argus/backend/service/views.py +0 -258
- argus/backend/template_filters.py +0 -27
- argus/backend/tests/__init__.py +0 -0
- argus/backend/tests/argus_web.test.yaml +0 -39
- argus/backend/tests/conftest.py +0 -44
- argus/backend/tests/results_service/__init__.py +0 -0
- argus/backend/tests/results_service/test_best_results.py +0 -70
- argus/backend/util/common.py +0 -65
- argus/backend/util/config.py +0 -38
- argus/backend/util/encoders.py +0 -41
- argus/backend/util/logsetup.py +0 -81
- argus/backend/util/module_loaders.py +0 -30
- argus/backend/util/send_email.py +0 -91
- argus/client/generic_result_old.py +0 -143
- argus/db/.gitkeep +0 -0
- argus/db/argus_json.py +0 -14
- argus/db/cloud_types.py +0 -125
- argus/db/config.py +0 -135
- argus/db/db_types.py +0 -139
- argus/db/interface.py +0 -370
- argus/db/testrun.py +0 -740
- argus/db/utils.py +0 -15
- argus_alm-0.12.9.dist-info/RECORD +0 -96
- /argus/{backend → common}/__init__.py +0 -0
- /argus/{backend/util → common}/enums.py +0 -0
- /argus/{backend/plugins/sct/types.py → common/sct_types.py} +0 -0
- /argus/{backend/plugins/sirenada/types.py → common/sirenada_types.py} +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/LICENSE +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/WHEEL +0 -0
- {argus_alm-0.12.9.dist-info → argus_alm-0.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import uuid
|
|
3
|
-
from dataclasses import asdict
|
|
4
|
-
from typing import Optional, Dict
|
|
5
|
-
|
|
6
|
-
from _pytest.fixtures import fixture
|
|
7
|
-
|
|
8
|
-
from argus.backend.plugins.sct.testrun import SCTTestRunSubmissionRequest
|
|
9
|
-
from argus.client.generic_result import GenericResultTable, ColumnMetadata, ResultType, ValidationRule
|
|
10
|
-
|
|
11
|
-
LOGGER = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
@fixture(autouse=True, scope='session')
|
|
14
|
-
def release(release_manager_service):
|
|
15
|
-
return release_manager_service.create_release("best_results", "best_results", False)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@fixture(autouse=True, scope='session')
|
|
19
|
-
def group(release_manager_service, release):
|
|
20
|
-
return release_manager_service.create_group("br_group", "best_results", build_system_id="best_results", release_id=str(release.id))
|
|
21
|
-
|
|
22
|
-
def get_fake_test_run(
|
|
23
|
-
schema_version: str = "1.0.0",
|
|
24
|
-
run_id: str = str(uuid.uuid4()),
|
|
25
|
-
job_name: str = "default_job_name",
|
|
26
|
-
job_url: str = "http://example.com",
|
|
27
|
-
started_by: str = "default_user",
|
|
28
|
-
commit_id: str = "default_commit_id",
|
|
29
|
-
sct_config: dict | None = None,
|
|
30
|
-
origin_url: str | None = None,
|
|
31
|
-
branch_name: str | None = "main",
|
|
32
|
-
runner_public_ip: str | None = None,
|
|
33
|
-
runner_private_ip: str | None = None
|
|
34
|
-
) -> tuple[str, dict]:
|
|
35
|
-
return "scylla-cluster-tests", asdict(SCTTestRunSubmissionRequest(
|
|
36
|
-
schema_version=schema_version,
|
|
37
|
-
run_id=run_id,
|
|
38
|
-
job_name=job_name,
|
|
39
|
-
job_url=job_url,
|
|
40
|
-
started_by=started_by,
|
|
41
|
-
commit_id=commit_id,
|
|
42
|
-
sct_config=sct_config,
|
|
43
|
-
origin_url=origin_url,
|
|
44
|
-
branch_name=branch_name,
|
|
45
|
-
runner_public_ip=runner_public_ip,
|
|
46
|
-
runner_private_ip=runner_private_ip
|
|
47
|
-
))
|
|
48
|
-
|
|
49
|
-
class SampleTable(GenericResultTable):
|
|
50
|
-
class Meta:
|
|
51
|
-
name = "Test Table Name"
|
|
52
|
-
description = "Test Table Description"
|
|
53
|
-
Columns = [ColumnMetadata(name="float col name", unit="ms", type=ResultType.FLOAT, higher_is_better=False),
|
|
54
|
-
ColumnMetadata(name="int col name", unit="ms", type=ResultType.INTEGER, higher_is_better=False),
|
|
55
|
-
ColumnMetadata(name="duration col name", unit="s", type=ResultType.DURATION, higher_is_better=False),
|
|
56
|
-
ColumnMetadata(name="non tracked col name", unit="", type=ResultType.FLOAT),
|
|
57
|
-
ColumnMetadata(name="text col name", unit="", type=ResultType.TEXT),
|
|
58
|
-
]
|
|
59
|
-
ValidationRules = {"float col name": ValidationRule(best_abs=4),
|
|
60
|
-
"int col name": ValidationRule(best_pct=50, best_abs=5),
|
|
61
|
-
"duration col name": ValidationRule(fixed_limit=590)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
def test_argus_tracks_best_result(release_manager_service, client_service, release, group):
|
|
65
|
-
test = release_manager_service.create_test('track_best_result', 'track_best_result', 'track_best_result', 'track_best_result',
|
|
66
|
-
group_id=str(group.id), release_id=str(release.id), plugin_name='sct')
|
|
67
|
-
print(test)
|
|
68
|
-
LOGGER.warning(f"available plugins: {client_service.PLUGINS}")
|
|
69
|
-
client_service.submit_run(*get_fake_test_run())
|
|
70
|
-
assert test
|
argus/backend/util/common.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from itertools import islice
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Callable, Iterable
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from flask import Request, Response, g
|
|
7
|
-
|
|
8
|
-
from argus.backend.models.web import User
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
LOGGER = logging.getLogger(__name__)
|
|
12
|
-
FlaskView = Callable[..., Response]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def first(iterable, value, key: Callable = None, predicate: Callable = None):
|
|
16
|
-
for elem in iterable:
|
|
17
|
-
if predicate and predicate(elem, value):
|
|
18
|
-
return elem
|
|
19
|
-
elif key and key(elem) == value:
|
|
20
|
-
return elem
|
|
21
|
-
elif elem == value:
|
|
22
|
-
return elem
|
|
23
|
-
return None
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def chunk(iterable: Iterable, slice_size = 90):
|
|
27
|
-
it = iter(iterable)
|
|
28
|
-
return iter(lambda: list(islice(it, slice_size)), [])
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def check_scheduled_test(test, group, testname):
|
|
32
|
-
return testname in (f"{group}/{test}", test)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def strip_html_tags(text: str):
|
|
36
|
-
return text.replace("<", "<").replace(">", ">")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def convert_str_list_to_uuid(lst: list[str]) -> list[UUID]:
|
|
40
|
-
return [UUID(s) for s in lst]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_payload(client_request: Request) -> dict:
|
|
44
|
-
if not client_request.is_json:
|
|
45
|
-
raise Exception(
|
|
46
|
-
"Content-Type mismatch, expected application/json, got:",
|
|
47
|
-
client_request.content_type
|
|
48
|
-
)
|
|
49
|
-
request_payload = client_request.get_json()
|
|
50
|
-
|
|
51
|
-
return request_payload
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def current_user() -> User:
|
|
55
|
-
return g.user
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def get_build_number(build_job_url: str) -> int | None:
|
|
59
|
-
build_number = build_job_url.rstrip("/").split("/")[-1] if build_job_url else -1
|
|
60
|
-
if build_number:
|
|
61
|
-
try:
|
|
62
|
-
return int(build_number)
|
|
63
|
-
except ValueError:
|
|
64
|
-
LOGGER.error("Error parsing build number from %s: got %s as build_number", build_job_url, build_number)
|
|
65
|
-
return None
|
argus/backend/util/config.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import yaml
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
LOGGER = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Config:
|
|
11
|
-
CONFIG = None
|
|
12
|
-
CONFIG_PATHS = [
|
|
13
|
-
Path(__file__).parents[3] / "config" / "argus_web.yaml",
|
|
14
|
-
Path("argus_web.yaml"),
|
|
15
|
-
Path("../config/argus_web.yaml"),
|
|
16
|
-
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
@classmethod
|
|
20
|
-
def locate_argus_web_config(cls) -> Path:
|
|
21
|
-
for config in cls.CONFIG_PATHS:
|
|
22
|
-
if config.exists():
|
|
23
|
-
return config
|
|
24
|
-
else:
|
|
25
|
-
LOGGER.debug("Tried %s as config, not found.", config)
|
|
26
|
-
|
|
27
|
-
raise Exception("Failed to locate web application config file!")
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def load_yaml_config(cls) -> dict:
|
|
31
|
-
if cls.CONFIG:
|
|
32
|
-
return cls.CONFIG
|
|
33
|
-
path = cls.locate_argus_web_config()
|
|
34
|
-
with open(path, "rt", encoding="utf-8") as file:
|
|
35
|
-
config = yaml.safe_load(file)
|
|
36
|
-
|
|
37
|
-
cls.CONFIG = config
|
|
38
|
-
return config
|
argus/backend/util/encoders.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
import logging
|
|
3
|
-
from json.encoder import JSONEncoder
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from flask.json.provider import DefaultJSONProvider
|
|
7
|
-
import cassandra.cqlengine.usertype as ut
|
|
8
|
-
import cassandra.cqlengine.models as m
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
LOGGER = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
class ArgusJSONEncoder(JSONEncoder):
|
|
14
|
-
def default(self, o):
|
|
15
|
-
match o:
|
|
16
|
-
case UUID():
|
|
17
|
-
return str(o)
|
|
18
|
-
case ut.UserType():
|
|
19
|
-
return dict(o.items())
|
|
20
|
-
case m.Model():
|
|
21
|
-
return dict(o.items())
|
|
22
|
-
case datetime():
|
|
23
|
-
return o.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
24
|
-
case _:
|
|
25
|
-
return super().default(o)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ArgusJSONProvider(DefaultJSONProvider):
|
|
29
|
-
|
|
30
|
-
def default(self, o):
|
|
31
|
-
match o:
|
|
32
|
-
case UUID():
|
|
33
|
-
return str(o)
|
|
34
|
-
case ut.UserType():
|
|
35
|
-
return dict(o.items())
|
|
36
|
-
case m.Model():
|
|
37
|
-
return dict(o.items())
|
|
38
|
-
case datetime():
|
|
39
|
-
return o.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
40
|
-
case _:
|
|
41
|
-
return super().default(o)
|
argus/backend/util/logsetup.py
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from logging.config import dictConfig
|
|
3
|
-
from flask import has_request_context, request
|
|
4
|
-
|
|
5
|
-
# pylint: disable=line-too-long
|
|
6
|
-
LOG_FORMAT_REQUEST = "[%(levelcolor)s%(levelname)s%(colorreset)s] %(grey)s<%(remote_addr)s - %(url)s - %(endpoint)s>%(colorreset)s - %(module)s::%(funcName)s - %(message)s"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ArgusRequestLogFormatter(logging.Formatter):
|
|
10
|
-
yellow = "\x1b[33;20m"
|
|
11
|
-
red = "\x1b[31;20m"
|
|
12
|
-
blue = "\x1b[34;20m"
|
|
13
|
-
bold_red = "\x1b[31;1m"
|
|
14
|
-
reset = "\x1b[0m"
|
|
15
|
-
grey = "\x1b[38;2;200;200;200m"
|
|
16
|
-
color_map = {
|
|
17
|
-
logging.DEBUG: grey,
|
|
18
|
-
logging.INFO: blue,
|
|
19
|
-
logging.WARNING: yellow,
|
|
20
|
-
logging.ERROR: red,
|
|
21
|
-
logging.CRITICAL: bold_red
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
def format(self, record: logging.LogRecord) -> str:
|
|
25
|
-
record.grey = self.grey
|
|
26
|
-
record.colorreset = self.reset
|
|
27
|
-
record.levelcolor = self.color_map.get(record.levelno, self.grey)
|
|
28
|
-
if has_request_context():
|
|
29
|
-
record.url = request.url
|
|
30
|
-
record.remote_addr = request.remote_addr
|
|
31
|
-
record.endpoint = request.endpoint
|
|
32
|
-
else:
|
|
33
|
-
record.url = ''
|
|
34
|
-
record.remote_addr = ''
|
|
35
|
-
record.endpoint = ''
|
|
36
|
-
return super().format(record)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def setup_application_logging(log_level=logging.INFO):
|
|
40
|
-
dictConfig({
|
|
41
|
-
'version': 1,
|
|
42
|
-
'formatters': {
|
|
43
|
-
'request': {
|
|
44
|
-
'class': f"{__name__}.{ArgusRequestLogFormatter.__name__}",
|
|
45
|
-
'format': LOG_FORMAT_REQUEST,
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
'handlers': {
|
|
49
|
-
'main': {
|
|
50
|
-
'class': 'logging.StreamHandler',
|
|
51
|
-
'stream': 'ext://sys.stderr',
|
|
52
|
-
'formatter': 'request'
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
'loggers': {
|
|
56
|
-
'cassandra': {
|
|
57
|
-
'level': log_level,
|
|
58
|
-
'handlers': ['main']
|
|
59
|
-
},
|
|
60
|
-
'argus': {
|
|
61
|
-
'level': log_level,
|
|
62
|
-
'handlers': ['main']
|
|
63
|
-
},
|
|
64
|
-
'argus_backend': {
|
|
65
|
-
'level': log_level,
|
|
66
|
-
'handlers': ['main']
|
|
67
|
-
},
|
|
68
|
-
'werkzeug': {
|
|
69
|
-
'level': log_level,
|
|
70
|
-
'handlers': ['main']
|
|
71
|
-
},
|
|
72
|
-
'uwsgi': {
|
|
73
|
-
'level': log_level,
|
|
74
|
-
'handlers': ['main']
|
|
75
|
-
},
|
|
76
|
-
'__main__': {
|
|
77
|
-
'level': log_level,
|
|
78
|
-
'handlers': ['main']
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from functools import wraps
|
|
3
|
-
from typing import Callable
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def is_filter(filter_name: str) -> Callable:
|
|
7
|
-
def outer_wrapper(func):
|
|
8
|
-
func.is_filter = True
|
|
9
|
-
func.filter_name = filter_name
|
|
10
|
-
|
|
11
|
-
@wraps(func)
|
|
12
|
-
def wrapper(*args, **kwargs):
|
|
13
|
-
return func(*args, **kwargs)
|
|
14
|
-
|
|
15
|
-
return wrapper
|
|
16
|
-
|
|
17
|
-
return outer_wrapper
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def export_functions(module_name: str, attr: str) -> list[Callable]:
|
|
21
|
-
module = sys.modules[module_name]
|
|
22
|
-
funcs = []
|
|
23
|
-
|
|
24
|
-
for member in dir(module):
|
|
25
|
-
export = getattr(module, member)
|
|
26
|
-
applicable_export = getattr(export, attr, False)
|
|
27
|
-
if applicable_export:
|
|
28
|
-
funcs.append(export)
|
|
29
|
-
|
|
30
|
-
return funcs
|
argus/backend/util/send_email.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import smtplib
|
|
2
|
-
from typing import List, Set
|
|
3
|
-
from smtplib import SMTPException
|
|
4
|
-
|
|
5
|
-
from email.mime.multipart import MIMEMultipart
|
|
6
|
-
from email.mime.text import MIMEText
|
|
7
|
-
from flask import current_app
|
|
8
|
-
from flask import render_template
|
|
9
|
-
|
|
10
|
-
class Email:
|
|
11
|
-
# pylint: disable=too-many-instance-attributes
|
|
12
|
-
"""
|
|
13
|
-
Responsible for sending emails
|
|
14
|
-
"""
|
|
15
|
-
_attachments_size_limit = 10485760 # 10Mb = 20 * 1024 * 1024
|
|
16
|
-
_body_size_limit = 26214400 # 25Mb = 20 * 1024 * 1024
|
|
17
|
-
|
|
18
|
-
def __init__(self, init_connection=True):
|
|
19
|
-
self.sender: str = ""
|
|
20
|
-
self._password: str = ""
|
|
21
|
-
self._user: str = ""
|
|
22
|
-
self._server_host: str = ""
|
|
23
|
-
self._server_port: int = 0
|
|
24
|
-
self._connection: smtplib.SMTP | None = None
|
|
25
|
-
self._retrieve_credentials()
|
|
26
|
-
if init_connection:
|
|
27
|
-
self._connect()
|
|
28
|
-
|
|
29
|
-
def _retrieve_credentials(self):
|
|
30
|
-
self.sender = current_app.config["EMAIL_SENDER"]
|
|
31
|
-
self._password = current_app.config["EMAIL_SENDER_PASS"]
|
|
32
|
-
self._user = current_app.config["EMAIL_SENDER_USER"]
|
|
33
|
-
self._server_host = current_app.config["EMAIL_SERVER"]
|
|
34
|
-
self._server_port = int(current_app.config["EMAIL_SERVER_PORT"])
|
|
35
|
-
|
|
36
|
-
def _connect(self):
|
|
37
|
-
try:
|
|
38
|
-
self._connection = smtplib.SMTP(host=self._server_host, port=self._server_port)
|
|
39
|
-
self._connection.ehlo()
|
|
40
|
-
self._connection.starttls()
|
|
41
|
-
self._connection.login(user=self._user, password=self._password)
|
|
42
|
-
except SMTPException as details:
|
|
43
|
-
current_app.logger.error("Failed to initialize smtp session %s", details)
|
|
44
|
-
|
|
45
|
-
def _is_connection_open(self):
|
|
46
|
-
if not self._connection:
|
|
47
|
-
return False
|
|
48
|
-
try:
|
|
49
|
-
status, _ = self._connection.noop()
|
|
50
|
-
except SMTPException:
|
|
51
|
-
status = -1
|
|
52
|
-
|
|
53
|
-
return True if status == 250 else False
|
|
54
|
-
|
|
55
|
-
def _prepare_email(self, subject:str,
|
|
56
|
-
content: str,
|
|
57
|
-
recipients: List[str],
|
|
58
|
-
html: bool = True): # pylint: disable=too-many-arguments
|
|
59
|
-
msg = MIMEMultipart()
|
|
60
|
-
msg['subject'] = subject
|
|
61
|
-
msg['from'] = self.sender
|
|
62
|
-
assert recipients, "No recipients provided"
|
|
63
|
-
msg['to'] = ','.join(recipients)
|
|
64
|
-
if html:
|
|
65
|
-
text_part = MIMEText(content, "html")
|
|
66
|
-
else:
|
|
67
|
-
text_part = MIMEText(content, "plain")
|
|
68
|
-
msg.attach(text_part)
|
|
69
|
-
email = msg.as_string()
|
|
70
|
-
return email
|
|
71
|
-
|
|
72
|
-
def send(self, subject, content, recipients, html=True): # pylint: disable=too-many-arguments
|
|
73
|
-
"""
|
|
74
|
-
:param subject: text
|
|
75
|
-
:param content: text/html
|
|
76
|
-
:param recipients: iterable, list of recipients
|
|
77
|
-
:param html: True/False
|
|
78
|
-
:param files: paths of the files that will be attached to the email
|
|
79
|
-
:return:
|
|
80
|
-
"""
|
|
81
|
-
email = self._prepare_email(subject, content, recipients, html)
|
|
82
|
-
self._send_email(recipients, email)
|
|
83
|
-
|
|
84
|
-
def _send_email(self, recipients, email):
|
|
85
|
-
if not self._is_connection_open():
|
|
86
|
-
self._connect()
|
|
87
|
-
self._connection.sendmail(self.sender, recipients, email)
|
|
88
|
-
|
|
89
|
-
def __del__(self):
|
|
90
|
-
if self._connection:
|
|
91
|
-
self._connection.quit()
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field, asdict
|
|
2
|
-
from typing import Dict, List, Tuple, Union, Type
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
from enum import Enum
|
|
5
|
-
import json
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Status(Enum):
|
|
10
|
-
PASS = 1
|
|
11
|
-
WARNING = 2
|
|
12
|
-
ERROR = 3
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ResultType(Enum):
|
|
16
|
-
INTEGER = int
|
|
17
|
-
FLOAT = float
|
|
18
|
-
TEXT = str
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class ColumnMetadata:
|
|
23
|
-
id: int
|
|
24
|
-
unit: str
|
|
25
|
-
type: ResultType
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass
|
|
29
|
-
class Result:
|
|
30
|
-
value: Union[int, float, str]
|
|
31
|
-
status: Status
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class ResultTableMeta(type):
|
|
35
|
-
def __new__(cls, name, bases, dct):
|
|
36
|
-
cls_instance = super().__new__(cls, name, bases, dct)
|
|
37
|
-
meta = dct.get('Meta')
|
|
38
|
-
|
|
39
|
-
if meta:
|
|
40
|
-
cls_instance.table_name = meta.table_name
|
|
41
|
-
cls_instance.columns_map = {col_name: ColumnMetadata(id=col_id, unit=unit, type=result_type)
|
|
42
|
-
for col_name, (col_id, unit, result_type) in meta.Columns.items()}
|
|
43
|
-
cls_instance.rows_map = {row_name: row_id
|
|
44
|
-
for row_name, row_id in meta.Rows.items()}
|
|
45
|
-
return cls_instance
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class Row:
|
|
49
|
-
def __init__(self, columns_map: Dict[str, ColumnMetadata]):
|
|
50
|
-
self.columns_map = columns_map
|
|
51
|
-
self.data: Dict[str, Result] = {}
|
|
52
|
-
|
|
53
|
-
def __getitem__(self, column_name: str) -> Result:
|
|
54
|
-
return self.data[column_name]
|
|
55
|
-
|
|
56
|
-
def __setitem__(self, column_name: str, result: Result):
|
|
57
|
-
if column_name not in self.columns_map:
|
|
58
|
-
raise ValueError(f"Column name '{column_name}' not found in columns_map.")
|
|
59
|
-
column_metadata = self.columns_map[column_name]
|
|
60
|
-
if not isinstance(result.value, column_metadata.type.value):
|
|
61
|
-
raise ValueError(f"Value {result.value} for column '{column_name}' is not of type {column_metadata.type.name}")
|
|
62
|
-
self.data[column_name] = result
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclass
|
|
66
|
-
class BaseResultTable(metaclass=ResultTableMeta):
|
|
67
|
-
results: Dict[str, Row] = field(default_factory=dict)
|
|
68
|
-
|
|
69
|
-
def as_dict(self) -> dict:
|
|
70
|
-
results_list = []
|
|
71
|
-
for row_name, row in self.results.items():
|
|
72
|
-
row_id = self.rows_map.get(row_name)
|
|
73
|
-
for column_name, result in row.data.items():
|
|
74
|
-
column_metadata = self.columns_map.get(column_name)
|
|
75
|
-
results_list.append({
|
|
76
|
-
"column_id": column_metadata.id,
|
|
77
|
-
"row_id": row_id,
|
|
78
|
-
"result": result.value,
|
|
79
|
-
"status": result.status.value
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
meta_info = {
|
|
83
|
-
"table_name": self.table_name,
|
|
84
|
-
"columns": {name: {"id": meta.id, "unit": meta.unit, "type": meta.type.name} for name, meta in self.columns_map.items()},
|
|
85
|
-
"rows": self.rows_map
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
"meta": meta_info,
|
|
89
|
-
"results": results_list
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
def to_json(self) -> str:
|
|
93
|
-
dict_result = self.as_dict()
|
|
94
|
-
return json.dumps(dict_result, default=str)
|
|
95
|
-
|
|
96
|
-
def __getitem__(self, row_name: str) -> Row:
|
|
97
|
-
if row_name not in self.rows_map:
|
|
98
|
-
raise ValueError(f"Row name '{row_name}' not found in rows_map.")
|
|
99
|
-
if row_name not in self.results:
|
|
100
|
-
self.results[row_name] = Row(self.columns_map)
|
|
101
|
-
return self.results[row_name]
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# Example of a specific result table with its own metadata
|
|
105
|
-
class LatencyResultTable(BaseResultTable):
|
|
106
|
-
class Meta:
|
|
107
|
-
table_name = "latency_percentile_write"
|
|
108
|
-
Columns = {
|
|
109
|
-
"latency": (1, "ms", ResultType.FLOAT),
|
|
110
|
-
"op_rate": (2, "ops", ResultType.INTEGER)
|
|
111
|
-
}
|
|
112
|
-
Rows = {
|
|
113
|
-
"mean": 1,
|
|
114
|
-
"p99": 2
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Client code example
|
|
119
|
-
if __name__ == "__main__":
|
|
120
|
-
class LatencyResultTable(BaseResultTable):
|
|
121
|
-
class Meta:
|
|
122
|
-
table_name = "latency_percentile_write"
|
|
123
|
-
Columns = {
|
|
124
|
-
"latency": (1, "ms", ResultType.FLOAT),
|
|
125
|
-
"op_rate": (2, "ops", ResultType.INTEGER)
|
|
126
|
-
}
|
|
127
|
-
Rows = {
|
|
128
|
-
"mean": 1,
|
|
129
|
-
"p99": 2
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
result_table = LatencyResultTable()
|
|
133
|
-
|
|
134
|
-
result_table["mean"]["latency"] = Result(value=1.1, status=Status.WARNING)
|
|
135
|
-
result_table["mean"]["op_rate"] = Result(value=59988, status=Status.ERROR)
|
|
136
|
-
result_table["p99"]["latency"] = Result(value=2.7, status=Status.PASS)
|
|
137
|
-
result_table["p99"]["op_rate"] = Result(value=59988, status=Status.WARNING)
|
|
138
|
-
|
|
139
|
-
from argus.client.sct.client import ArgusSCTClient
|
|
140
|
-
|
|
141
|
-
run_id = UUID("24e09748-bba4-47fd-a615-bf7ea2c425eb")
|
|
142
|
-
client = ArgusSCTClient(run_id, auth_token="UO+2GXL9XqSgcVJijWk5WnbPXPit5ot5nfkLAHAr7SaqROfSCWycabpp/wxyY8+I", base_url="http://localhost:5000")
|
|
143
|
-
client.submit_results(result_table)
|
argus/db/.gitkeep
DELETED
|
File without changes
|
argus/db/argus_json.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
import json
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ArgusJSONEncoder(json.JSONEncoder):
|
|
7
|
-
def default(self, obj):
|
|
8
|
-
match obj:
|
|
9
|
-
case UUID():
|
|
10
|
-
return str(obj)
|
|
11
|
-
case datetime():
|
|
12
|
-
return obj.isoformat(sep=' ', timespec='milliseconds')
|
|
13
|
-
case _:
|
|
14
|
-
return json.JSONEncoder.default(self, obj)
|