pytbox 0.1.5__tar.gz → 0.1.7__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.
Potentially problematic release.
This version of pytbox might be problematic. Click here for more details.
- {pytbox-0.1.5/src/pytbox.egg-info → pytbox-0.1.7}/PKG-INFO +1 -1
- {pytbox-0.1.5 → pytbox-0.1.7}/pyproject.toml +1 -1
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/alert/ping.py +0 -1
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/alicloud/sls.py +9 -6
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/base.py +28 -1
- pytbox-0.1.7/src/pytbox/database/victoriametrics.py +312 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/log/logger.py +22 -6
- pytbox-0.1.7/src/pytbox/utils/cronjob.py +79 -0
- {pytbox-0.1.5 → pytbox-0.1.7/src/pytbox.egg-info}/PKG-INFO +1 -1
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox.egg-info/SOURCES.txt +2 -1
- pytbox-0.1.7/tests/test_load_jsonfile.py +14 -0
- pytbox-0.1.7/tests/test_logger.py +20 -0
- pytbox-0.1.7/tests/test_victoriametrics.py +28 -0
- pytbox-0.1.5/src/pytbox/database/victoriametrics.py +0 -149
- pytbox-0.1.5/src/pytbox/utils/ping_checker.py +0 -1
- pytbox-0.1.5/tests/test_logger.py +0 -15
- pytbox-0.1.5/tests/test_victoriametrics.py +0 -22
- {pytbox-0.1.5 → pytbox-0.1.7}/MANIFEST.in +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/README.md +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/setup.cfg +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/alert/alert_handler.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/build_config.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/instances.toml +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.disk/disk.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.mem/mem.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.net/net.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.ping/ping.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/categraf/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/categraf/commands.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/commands/vm.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/common/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/common/options.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/common/utils.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/formatters/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/formatters/output.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli/main.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/cli.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/common/__init__.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/database/mongo.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/dida365.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/excel.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/feishu/client.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/feishu/endpoints.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/feishu/errors.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/feishu/helpers.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/feishu/typing.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/log/victorialog.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/mail/alimail.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/mail/client.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/mail/mail_detail.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/network/meraki.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/onepassword_connect.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/onepassword_sa.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/pyjira.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/env.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/load_config.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/load_vm_devfile.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/response.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/richutils.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/utils/timeutils.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/vmware.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/win/ad.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox.egg-info/dependency_links.txt +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox.egg-info/entry_points.txt +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox.egg-info/requires.txt +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox.egg-info/top_level.txt +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/tests/test_base.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/tests/test_feishu.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/tests/test_onepassword_connect.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/tests/test_onepassword_sa.py +0 -0
- {pytbox-0.1.5 → pytbox-0.1.7}/tests/test_vmware.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytbox"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)"
|
|
9
9
|
authors = [{ name = "mingming hou", email = "houm01@foxmail.com" }]
|
|
10
10
|
license = "MIT"
|
|
@@ -5,19 +5,21 @@ from typing import Literal
|
|
|
5
5
|
from aliyun.log import GetLogsRequest, LogItem, PutLogsRequest
|
|
6
6
|
from aliyun.log import LogClient as SlsLogClient
|
|
7
7
|
from aliyun.log.auth import AUTH_VERSION_4
|
|
8
|
-
from ..utils.env import check_env
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class AliCloudSls:
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
'''
|
|
13
|
+
pip install -U aliyun-log-python-sdk
|
|
14
|
+
'''
|
|
15
|
+
def __init__(self, access_key_id: str=None, access_key_secret: str=None, project: str=None, logstore: str=None, env: str='prod'):
|
|
15
16
|
# 日志服务的服务接入点
|
|
16
17
|
self.endpoint = "cn-shanghai.log.aliyuncs.com"
|
|
17
18
|
# 创建 LogClient 实例,使用 V4 签名,根据实际情况填写 region,这里以杭州为例
|
|
18
19
|
self.client = SlsLogClient(self.endpoint, access_key_id, access_key_secret, auth_version=AUTH_VERSION_4, region='cn-shanghai')
|
|
19
20
|
self.project = project
|
|
20
21
|
self.logstore = logstore
|
|
22
|
+
self.env = env
|
|
21
23
|
|
|
22
24
|
def get_logs(self, project_name, logstore_name, query, from_time, to_time):
|
|
23
25
|
logstore_index = {'line': {
|
|
@@ -57,7 +59,7 @@ class AliCloudSls:
|
|
|
57
59
|
log_group = []
|
|
58
60
|
log_item = LogItem()
|
|
59
61
|
contents = [
|
|
60
|
-
('env',
|
|
62
|
+
('env', self.env),
|
|
61
63
|
('level', level),
|
|
62
64
|
('app', app),
|
|
63
65
|
('msg', msg),
|
|
@@ -69,8 +71,9 @@ class AliCloudSls:
|
|
|
69
71
|
log_item.set_contents(contents)
|
|
70
72
|
log_group.append(log_item)
|
|
71
73
|
request = PutLogsRequest(self.project, self.logstore, topic, "", log_group, compress=False)
|
|
72
|
-
self.client.put_logs(request)
|
|
73
|
-
|
|
74
|
+
r = self.client.put_logs(request)
|
|
75
|
+
return r
|
|
76
|
+
|
|
74
77
|
def put_logs_for_meraki(self, alert):
|
|
75
78
|
log_group = []
|
|
76
79
|
log_item = LogItem()
|
|
@@ -15,6 +15,9 @@ from pytbox.vmware import VMwareClient
|
|
|
15
15
|
from pytbox.pyjira import PyJira
|
|
16
16
|
from pytbox.mail.client import MailClient
|
|
17
17
|
from pytbox.mail.alimail import AliMail
|
|
18
|
+
from pytbox.alicloud.sls import AliCloudSls
|
|
19
|
+
from pytbox.utils.cronjob import cronjob_counter
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
config = load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=os.environ.get('oc_vault_id'))
|
|
20
23
|
|
|
@@ -53,6 +56,19 @@ def get_logger(app):
|
|
|
53
56
|
mongo=get_mongo('alert_program')
|
|
54
57
|
)
|
|
55
58
|
|
|
59
|
+
def get_logger_sls(app):
|
|
60
|
+
return AppLogger(
|
|
61
|
+
app_name=app,
|
|
62
|
+
enable_sls=True,
|
|
63
|
+
feishu=feishu,
|
|
64
|
+
dida=dida,
|
|
65
|
+
mongo=get_mongo('alert_program'),
|
|
66
|
+
sls_access_key_id=config['alicloud']['account1']['access_key_id'],
|
|
67
|
+
sls_access_key_secret=config['alicloud']['account1']['access_key_secret'],
|
|
68
|
+
sls_project=config['alicloud']['account1']['project'],
|
|
69
|
+
sls_logstore=config['alicloud']['account1']['logstore']
|
|
70
|
+
)
|
|
71
|
+
|
|
56
72
|
# ad_dev = ADClient(
|
|
57
73
|
# server=config['ad']['dev']['AD_SERVER'],
|
|
58
74
|
# base_dn=config['ad']['dev']['BASE_DN'],
|
|
@@ -85,4 +101,15 @@ pyjira = PyJira(
|
|
|
85
101
|
|
|
86
102
|
mail_163 = MailClient(mail_address=config['mail']['163']['mail_address'], password=config['mail']['163']['password'])
|
|
87
103
|
mail_qq = MailClient(mail_address=config['mail']['qq']['mail_address'], password=config['mail']['qq']['password'])
|
|
88
|
-
ali_mail = AliMail(mail_address=config['mail']['aliyun']['mail_address'], client_id=config['mail']['aliyun']['client_id'], client_secret=config['mail']['aliyun']['client_secret'])
|
|
104
|
+
ali_mail = AliMail(mail_address=config['mail']['aliyun']['mail_address'], client_id=config['mail']['aliyun']['client_id'], client_secret=config['mail']['aliyun']['client_secret'])
|
|
105
|
+
|
|
106
|
+
sls = AliCloudSls(
|
|
107
|
+
access_key_id=config['alicloud']['account1']['access_key_id'],
|
|
108
|
+
access_key_secret=config['alicloud']['account1']['access_key_secret'],
|
|
109
|
+
project=config['alicloud']['account1']['project'],
|
|
110
|
+
logstore=config['alicloud']['account1']['logstore']
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_cronjob_counter(app_type='', app='', comment=None, schedule_interval=None, schedule_cron=None):
|
|
115
|
+
return cronjob_counter(vm=vm, log=get_logger('cronjob_counter'), app_type=app_type, app=app, comment=comment, schedule_interval=schedule_interval, schedule_cron=schedule_cron)
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import json
|
|
5
|
+
from typing import Literal, Optional, Dict, List
|
|
6
|
+
import requests
|
|
7
|
+
from ..utils.response import ReturnResponse
|
|
8
|
+
from ..utils.load_vm_devfile import load_dev_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VictoriaMetrics:
|
|
12
|
+
|
|
13
|
+
def __init__(self, url: str='', timeout: int=3, env: str='prod') -> None:
|
|
14
|
+
self.url = url
|
|
15
|
+
self.timeout = timeout
|
|
16
|
+
self.session = requests.Session()
|
|
17
|
+
self.session.headers.update({
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Accept': 'application/json'
|
|
20
|
+
})
|
|
21
|
+
self.env = env
|
|
22
|
+
|
|
23
|
+
def insert(self, metric_name: str = '', labels: Dict[str, str] = None,
|
|
24
|
+
value: List[float] = None, timestamp: int = None) -> ReturnResponse:
|
|
25
|
+
"""插入指标数据。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
metric_name: 指标名称
|
|
29
|
+
labels: 标签字典
|
|
30
|
+
value: 值列表
|
|
31
|
+
timestamp: 时间戳(毫秒),默认为当前时间
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
requests.RequestException: 当请求失败时抛出
|
|
35
|
+
"""
|
|
36
|
+
if labels is None:
|
|
37
|
+
labels = {}
|
|
38
|
+
if value is None:
|
|
39
|
+
value = 1
|
|
40
|
+
if timestamp is None:
|
|
41
|
+
timestamp = int(time.time() * 1000)
|
|
42
|
+
|
|
43
|
+
url = f"{self.url}/api/v1/import"
|
|
44
|
+
data = {
|
|
45
|
+
"metric": {
|
|
46
|
+
"__name__": metric_name,
|
|
47
|
+
**labels
|
|
48
|
+
},
|
|
49
|
+
"values": [value],
|
|
50
|
+
"timestamps": [timestamp]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
response = requests.post(url, json=data, timeout=self.timeout)
|
|
55
|
+
return ReturnResponse(code=0, msg=f"数据插入成功,状态码: {response.status_code}, metric_name: {metric_name}, labels: {labels}, value: {value}, timestamp: {timestamp}")
|
|
56
|
+
except requests.RequestException as e:
|
|
57
|
+
return ReturnResponse(code=1, msg=f"数据插入失败: {e}")
|
|
58
|
+
|
|
59
|
+
def query(self, query: str=None, output_format: Literal['json']=None) -> ReturnResponse:
|
|
60
|
+
'''
|
|
61
|
+
查询指标数据
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
query (str): 查询语句
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
dict: 查询结果
|
|
68
|
+
'''
|
|
69
|
+
url = f"{self.url}/prometheus/api/v1/query"
|
|
70
|
+
r = requests.get(
|
|
71
|
+
url,
|
|
72
|
+
timeout=self.timeout,
|
|
73
|
+
params={"query": query}
|
|
74
|
+
)
|
|
75
|
+
res_json = r.json()
|
|
76
|
+
status = res_json.get("status")
|
|
77
|
+
result = res_json.get("data", {}).get("result", [])
|
|
78
|
+
is_json = output_format == 'json'
|
|
79
|
+
|
|
80
|
+
if status == "success":
|
|
81
|
+
if result:
|
|
82
|
+
code = 0
|
|
83
|
+
msg = f"[{query}] 查询成功!"
|
|
84
|
+
data = result
|
|
85
|
+
else:
|
|
86
|
+
code = 2
|
|
87
|
+
msg = f"[{query}] 没有查询到结果"
|
|
88
|
+
data = res_json
|
|
89
|
+
else:
|
|
90
|
+
code = 1
|
|
91
|
+
msg = f"[{query}] 查询失败: {res_json.get('error')}"
|
|
92
|
+
data = res_json
|
|
93
|
+
|
|
94
|
+
resp = ReturnResponse(code=code, msg=msg, data=data)
|
|
95
|
+
|
|
96
|
+
if is_json:
|
|
97
|
+
json_result = json.dumps(resp.__dict__, ensure_ascii=False)
|
|
98
|
+
return json_result
|
|
99
|
+
else:
|
|
100
|
+
return resp
|
|
101
|
+
|
|
102
|
+
def query_range(self, query):
|
|
103
|
+
'''
|
|
104
|
+
查询指标数据
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
query (str): 查询语句
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
dict: 查询结果
|
|
111
|
+
'''
|
|
112
|
+
url = f"{self.url}/prometheus/api/v1/query_range"
|
|
113
|
+
|
|
114
|
+
data = {
|
|
115
|
+
'query': query,
|
|
116
|
+
'start': '-1d',
|
|
117
|
+
'step': '1h'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
r = requests.post(url, data=data, timeout=self.timeout)
|
|
121
|
+
res_json = r.json()
|
|
122
|
+
print(res_json)
|
|
123
|
+
# status = res_json.get("status")
|
|
124
|
+
# result = res_json.get("data", {}).get("result", [])
|
|
125
|
+
# is_json = output_format == 'json'
|
|
126
|
+
|
|
127
|
+
# if status == "success":
|
|
128
|
+
# if result:
|
|
129
|
+
# code = 0
|
|
130
|
+
# msg = f"[{query}] 查询成功!"
|
|
131
|
+
# data = result
|
|
132
|
+
# else:
|
|
133
|
+
# code = 2
|
|
134
|
+
# msg = f"[{query}] 没有查询到结果"
|
|
135
|
+
# data = res_json
|
|
136
|
+
# else:
|
|
137
|
+
# code = 1
|
|
138
|
+
# msg = f"[{query}] 查询失败: {res_json.get('error')}"
|
|
139
|
+
# data = res_json
|
|
140
|
+
|
|
141
|
+
# resp = ReturnResponse(code=code, msg=msg, data=data)
|
|
142
|
+
|
|
143
|
+
# if is_json:
|
|
144
|
+
# json_result = json.dumps(resp.__dict__, ensure_ascii=False)
|
|
145
|
+
# return json_result
|
|
146
|
+
# else:
|
|
147
|
+
# return resp
|
|
148
|
+
def get_labels(self, metric_name: str) -> ReturnResponse:
|
|
149
|
+
url = f"{self.url}/api/v1/series?match[]={metric_name}"
|
|
150
|
+
response = requests.get(url, timeout=self.timeout)
|
|
151
|
+
results = response.json()
|
|
152
|
+
if results['status'] == 'success':
|
|
153
|
+
return ReturnResponse(code=0, msg=f"metric name: {metric_name} 获取到 {len(results['data'])} 条数据", data=results['data'])
|
|
154
|
+
else:
|
|
155
|
+
return ReturnResponse(code=1, msg=f"metric name: {metric_name} 查询失败")
|
|
156
|
+
|
|
157
|
+
def check_ping_result(self, target: str, last_minute: int=10, env: str='prod', dev_file: str='') -> ReturnResponse:
|
|
158
|
+
'''
|
|
159
|
+
检查ping结果
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
target (str): 目标地址
|
|
163
|
+
last_minute (int, optional): 最近多少分钟. Defaults to 10.
|
|
164
|
+
env (str, optional): 环境. Defaults to 'prod'.
|
|
165
|
+
dev_file (str, optional): 开发文件. Defaults to ''.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
ReturnResponse:
|
|
169
|
+
code = 0 正常, code = 1 异常, code = 2 没有查询到数据, 建议将其判断为正常
|
|
170
|
+
'''
|
|
171
|
+
if target:
|
|
172
|
+
# 这里需要在字符串中保留 {},同时插入 target,可以用双大括号转义
|
|
173
|
+
query = f"ping_result_code{{target='{target}'}}"
|
|
174
|
+
else:
|
|
175
|
+
query = "ping_result_code"
|
|
176
|
+
|
|
177
|
+
if last_minute:
|
|
178
|
+
query = query + f"[{last_minute}m]"
|
|
179
|
+
|
|
180
|
+
if env == 'dev':
|
|
181
|
+
r = load_dev_file(dev_file)
|
|
182
|
+
else:
|
|
183
|
+
r = self.query(query=query)
|
|
184
|
+
|
|
185
|
+
if r.code == 0:
|
|
186
|
+
values = r.data[0]['values']
|
|
187
|
+
if len(values) == 2 and values[1] == "0":
|
|
188
|
+
code = 0
|
|
189
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
190
|
+
else:
|
|
191
|
+
if all(str(item[1]) == "1" for item in values):
|
|
192
|
+
code = 1
|
|
193
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
|
|
194
|
+
else:
|
|
195
|
+
code = 0
|
|
196
|
+
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
197
|
+
elif r.code == 2:
|
|
198
|
+
code = 2
|
|
199
|
+
msg = f"没有查询到 {target} 最近 {last_minute} 分钟的ping结果!"
|
|
200
|
+
try:
|
|
201
|
+
data = r.data[0]
|
|
202
|
+
except KeyError:
|
|
203
|
+
data = r.data
|
|
204
|
+
|
|
205
|
+
return ReturnResponse(code=code, msg=msg, data=data)
|
|
206
|
+
|
|
207
|
+
def check_interface_rate(self,
|
|
208
|
+
direction: Literal['in', 'out'],
|
|
209
|
+
sysName: str,
|
|
210
|
+
ifName:str,
|
|
211
|
+
last_minutes: Optional[int] = None
|
|
212
|
+
) -> ReturnResponse:
|
|
213
|
+
"""查询指定设备的入方向总流量速率(bps)。
|
|
214
|
+
|
|
215
|
+
使用 PromQL 对 `snmp_interface_ifHCInOctets` 进行速率计算并聚合到设备级别,
|
|
216
|
+
将结果从字节每秒转换为比特每秒(乘以 8)。
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
sysName: 设备 `sysName` 标签值。
|
|
220
|
+
last_minutes: 计算速率的时间窗口(分钟)。未提供时默认使用 5 分钟窗口。
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
ReturnResponse: 查询结果包装。
|
|
224
|
+
"""
|
|
225
|
+
if direction == 'in':
|
|
226
|
+
query = f'(rate(snmp_interface_ifHCInOctets{{sysName="{sysName}", ifName="{ifName}"}}[{last_minutes}m])) * 8 / 1000000'
|
|
227
|
+
else:
|
|
228
|
+
query = f'(rate(snmp_interface_ifHCOutOctets{{sysName="{sysName}", ifName="{ifName}"}}[{last_minutes}m])) * 8 / 1000000'
|
|
229
|
+
r = self.query(query)
|
|
230
|
+
rate = r.data[0]['value'][1]
|
|
231
|
+
return int(float(rate))
|
|
232
|
+
|
|
233
|
+
def check_snmp_port_status(self, sysname: str=None, if_name: str=None, last_minute: int=5) -> ReturnResponse:
|
|
234
|
+
'''
|
|
235
|
+
查询端口状态
|
|
236
|
+
status code 可参考 SNMP 文件 https://mibbrowser.online/mibdb_search.php?mib=IF-MIB
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
sysname (_type_): 设备名称
|
|
240
|
+
if_name (_type_): _description_
|
|
241
|
+
last_minute (_type_): _description_
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
ReturnResponse:
|
|
245
|
+
code: 0, msg: , data: up,down
|
|
246
|
+
'''
|
|
247
|
+
q = f"""avg_over_time(snmp_interface_ifOperStatus{{sysName="{sysname}", ifName="{if_name}"}}[{last_minute}m])"""
|
|
248
|
+
r = self.query(query=q)
|
|
249
|
+
if r.code == 0:
|
|
250
|
+
status_code = r.data[0]['value'][1]
|
|
251
|
+
if status_code == 1:
|
|
252
|
+
status = 'up'
|
|
253
|
+
else:
|
|
254
|
+
status = 'down'
|
|
255
|
+
return ReturnResponse(code=0, msg=f"{sysname} {if_name} 最近 {last_minute} 分钟端口状态为 {status}", data=status)
|
|
256
|
+
else:
|
|
257
|
+
return r
|
|
258
|
+
|
|
259
|
+
def insert_cronjob_run_status(self,
|
|
260
|
+
app_type: Literal['alert', 'meraki', 'other']='other',
|
|
261
|
+
app: str='',
|
|
262
|
+
status_code: Literal[0, 1]=1,
|
|
263
|
+
comment: str=None,
|
|
264
|
+
schedule_interval: str=None,
|
|
265
|
+
schedule_cron: str=None
|
|
266
|
+
) -> ReturnResponse:
|
|
267
|
+
labels = {
|
|
268
|
+
"app": app,
|
|
269
|
+
"env": self.env,
|
|
270
|
+
}
|
|
271
|
+
if app_type:
|
|
272
|
+
labels['app_type'] = app_type
|
|
273
|
+
if comment:
|
|
274
|
+
labels['comment'] = comment
|
|
275
|
+
|
|
276
|
+
if schedule_interval:
|
|
277
|
+
labels['schedule_type'] = 'interval'
|
|
278
|
+
labels['schedule_interval'] = schedule_interval
|
|
279
|
+
|
|
280
|
+
if schedule_cron:
|
|
281
|
+
labels['schedule_type'] = 'cron'
|
|
282
|
+
labels['schedule_cron'] = schedule_cron
|
|
283
|
+
|
|
284
|
+
r = self.insert(metric_name="cronjob_run_status", labels=labels, value=status_code)
|
|
285
|
+
return r
|
|
286
|
+
|
|
287
|
+
def insert_cronjob_duration_seconds(self,
|
|
288
|
+
app_type: Literal['alert', 'meraki', 'other']='other',
|
|
289
|
+
app: str='',
|
|
290
|
+
duration_seconds: float=None,
|
|
291
|
+
comment: str=None,
|
|
292
|
+
schedule_interval: str=None,
|
|
293
|
+
schedule_cron: str=None
|
|
294
|
+
) -> ReturnResponse:
|
|
295
|
+
labels = {
|
|
296
|
+
"app": app,
|
|
297
|
+
"env": self.env
|
|
298
|
+
}
|
|
299
|
+
if app_type:
|
|
300
|
+
labels['app_type'] = app_type
|
|
301
|
+
if comment:
|
|
302
|
+
labels['comment'] = comment
|
|
303
|
+
|
|
304
|
+
if schedule_interval:
|
|
305
|
+
labels['schedule_type'] = 'interval'
|
|
306
|
+
labels['schedule_interval'] = schedule_interval
|
|
307
|
+
|
|
308
|
+
if schedule_cron:
|
|
309
|
+
labels['schedule_type'] = 'cron'
|
|
310
|
+
labels['schedule_cron'] = schedule_cron
|
|
311
|
+
r = self.insert(metric_name="cronjob_run_duration_seconds", labels=labels, value=duration_seconds)
|
|
312
|
+
return r
|
|
@@ -9,7 +9,7 @@ from ..database.mongo import Mongo
|
|
|
9
9
|
from ..feishu.client import Client as FeishuClient
|
|
10
10
|
from ..utils.timeutils import TimeUtils
|
|
11
11
|
from ..dida365 import Dida365
|
|
12
|
-
|
|
12
|
+
from ..alicloud.sls import AliCloudSls
|
|
13
13
|
|
|
14
14
|
logger.remove()
|
|
15
15
|
logger.add(sys.stdout, colorize=True, format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>")
|
|
@@ -31,7 +31,6 @@ class AppLogger:
|
|
|
31
31
|
feishu: FeishuClient=None,
|
|
32
32
|
dida: Dida365=None,
|
|
33
33
|
enable_sls: bool=False,
|
|
34
|
-
sls_url: str=None,
|
|
35
34
|
sls_access_key_id: str=None,
|
|
36
35
|
sls_access_key_secret: str=None,
|
|
37
36
|
sls_project: str=None,
|
|
@@ -49,10 +48,17 @@ class AppLogger:
|
|
|
49
48
|
self.stream = stream
|
|
50
49
|
self.victorialog = Victorialog(url=victorialog_url)
|
|
51
50
|
self.enable_victorialog = enable_victorialog
|
|
51
|
+
self.enable_sls = enable_sls
|
|
52
52
|
self.mongo = mongo
|
|
53
53
|
self.feishu = feishu
|
|
54
54
|
self.dida = dida
|
|
55
|
-
|
|
55
|
+
self.sls = AliCloudSls(
|
|
56
|
+
access_key_id=sls_access_key_id,
|
|
57
|
+
access_key_secret=sls_access_key_secret,
|
|
58
|
+
project=sls_project,
|
|
59
|
+
logstore=sls_logstore
|
|
60
|
+
)
|
|
61
|
+
|
|
56
62
|
def _get_caller_info(self) -> tuple[str, int, str]:
|
|
57
63
|
"""
|
|
58
64
|
获取调用者信息
|
|
@@ -78,6 +84,9 @@ class AppLogger:
|
|
|
78
84
|
logger.debug(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
|
|
79
85
|
if self.enable_victorialog:
|
|
80
86
|
self.victorialog.send_program_log(stream=self.stream, level="DEBUG", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
|
|
87
|
+
if self.enable_sls:
|
|
88
|
+
self.sls.put_logs(level="DEBUG", msg=message, app=self.app_name, caller_filename=caller_filename, caller_lineno=caller_lineno, caller_function=caller_function, call_full_filename=call_full_filename)
|
|
89
|
+
|
|
81
90
|
|
|
82
91
|
def info(self, message: str='', feishu_notify: bool=False):
|
|
83
92
|
"""记录信息级别日志"""
|
|
@@ -85,22 +94,28 @@ class AppLogger:
|
|
|
85
94
|
logger.info(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
|
|
86
95
|
if self.enable_victorialog:
|
|
87
96
|
r = self.victorialog.send_program_log(stream=self.stream, level="INFO", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
|
|
97
|
+
if self.enable_sls:
|
|
98
|
+
self.sls.put_logs(level="INFO", msg=message, app=self.app_name, caller_filename=caller_filename, caller_lineno=caller_lineno, caller_function=caller_function, call_full_filename=call_full_filename)
|
|
88
99
|
if feishu_notify:
|
|
89
100
|
self.feishu(message)
|
|
90
|
-
|
|
101
|
+
|
|
91
102
|
def warning(self, message: str):
|
|
92
103
|
"""记录警告级别日志"""
|
|
93
104
|
caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
|
|
94
105
|
logger.warning(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
|
|
95
106
|
if self.enable_victorialog:
|
|
96
107
|
self.victorialog.send_program_log(stream=self.stream, level="WARN", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
|
|
97
|
-
|
|
108
|
+
if self.enable_sls:
|
|
109
|
+
self.sls.put_logs(level="WARN", msg=message, app=self.app_name, caller_filename=caller_filename, caller_lineno=caller_lineno, caller_function=caller_function, call_full_filename=call_full_filename)
|
|
110
|
+
|
|
98
111
|
def error(self, message: str):
|
|
99
112
|
"""记录错误级别日志"""
|
|
100
113
|
caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
|
|
101
114
|
logger.error(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
|
|
102
115
|
if self.enable_victorialog:
|
|
103
116
|
self.victorialog.send_program_log(stream=self.stream, level="ERROR", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
|
|
117
|
+
if self.enable_sls:
|
|
118
|
+
self.sls.put_logs(level="ERROR", msg=message, app=self.app_name, caller_filename=caller_filename, caller_lineno=caller_lineno, caller_function=caller_function, call_full_filename=call_full_filename)
|
|
104
119
|
|
|
105
120
|
if self.feishu:
|
|
106
121
|
existing_message = self.mongo.collection.find_one({"message": message}, sort=[("time", -1)])
|
|
@@ -150,7 +165,8 @@ class AppLogger:
|
|
|
150
165
|
logger.critical(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
|
|
151
166
|
if self.enable_victorialog:
|
|
152
167
|
self.victorialog.send_program_log(stream=self.stream, level="CRITICAL", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
|
|
153
|
-
|
|
168
|
+
if self.enable_sls:
|
|
169
|
+
self.sls.put_logs(level="CRITICAL", msg=message, app=self.app_name, caller_filename=caller_filename, caller_lineno=caller_lineno, caller_function=caller_function, call_full_filename=call_full_filename)
|
|
154
170
|
|
|
155
171
|
# 使用示例
|
|
156
172
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from ..database.victoriametrics import VictoriaMetrics
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cronjob_counter(vm: VictoriaMetrics=None, log: str=None, app_type: Literal['']='', app='', comment=None, schedule_interval=None, schedule_cron=None):
|
|
10
|
+
"""计算函数运行时间的装饰器,支持记录到 VictoriaMetrics
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
app_type: 应用类型 ('alert', 'meraki', 'other')
|
|
14
|
+
app: 应用名称
|
|
15
|
+
comment: 备注信息
|
|
16
|
+
schedule_interval: 定时任务间隔(如 '1m', '5m')
|
|
17
|
+
schedule_cron: cron 表达式(如 '0 */5 * * *')
|
|
18
|
+
"""
|
|
19
|
+
def decorator(func):
|
|
20
|
+
@wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs):
|
|
22
|
+
start_time = time.time()
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
result = func(*args, **kwargs)
|
|
26
|
+
end_time = time.time()
|
|
27
|
+
elapsed_time = end_time - start_time
|
|
28
|
+
|
|
29
|
+
# 记录任务成功完成状态
|
|
30
|
+
vm.insert_cronjob_run_status(
|
|
31
|
+
app_type=app_type,
|
|
32
|
+
app=app,
|
|
33
|
+
status_code=1, # 0 表示成功完成
|
|
34
|
+
comment=f"成功完成: {comment}" if comment else "任务成功完成",
|
|
35
|
+
schedule_interval=schedule_interval,
|
|
36
|
+
schedule_cron=schedule_cron
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# 记录执行耗时
|
|
40
|
+
vm.insert_cronjob_duration_seconds(
|
|
41
|
+
app_type=app_type,
|
|
42
|
+
app=app,
|
|
43
|
+
duration_seconds=elapsed_time,
|
|
44
|
+
comment=comment,
|
|
45
|
+
schedule_interval=schedule_interval,
|
|
46
|
+
schedule_cron=schedule_cron
|
|
47
|
+
)
|
|
48
|
+
log.info(f"{app} 任务成功完成, 耗时 {elapsed_time:.2f} 秒")
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
end_time = time.time()
|
|
54
|
+
elapsed_time = end_time - start_time
|
|
55
|
+
|
|
56
|
+
# 记录任务失败状态
|
|
57
|
+
error_comment = f"执行出错: {str(e)}" if not comment else f"{comment} (出错: {str(e)})"
|
|
58
|
+
vm.insert_cronjob_run_status(
|
|
59
|
+
app_type=app_type,
|
|
60
|
+
app=app,
|
|
61
|
+
status_code=0, # 1 表示失败
|
|
62
|
+
comment=error_comment,
|
|
63
|
+
schedule_interval=schedule_interval,
|
|
64
|
+
schedule_cron=schedule_cron
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# 即使出错也记录耗时
|
|
68
|
+
vm.insert_cronjob_duration_seconds(
|
|
69
|
+
app_type=app_type,
|
|
70
|
+
app=app,
|
|
71
|
+
duration_seconds=elapsed_time,
|
|
72
|
+
comment=error_comment,
|
|
73
|
+
schedule_interval=schedule_interval,
|
|
74
|
+
schedule_cron=schedule_cron
|
|
75
|
+
)
|
|
76
|
+
log.error(f"任务失败: {error_comment}")
|
|
77
|
+
raise
|
|
78
|
+
return wrapper
|
|
79
|
+
return decorator
|
|
@@ -64,16 +64,17 @@ src/pytbox/mail/alimail.py
|
|
|
64
64
|
src/pytbox/mail/client.py
|
|
65
65
|
src/pytbox/mail/mail_detail.py
|
|
66
66
|
src/pytbox/network/meraki.py
|
|
67
|
+
src/pytbox/utils/cronjob.py
|
|
67
68
|
src/pytbox/utils/env.py
|
|
68
69
|
src/pytbox/utils/load_config.py
|
|
69
70
|
src/pytbox/utils/load_vm_devfile.py
|
|
70
|
-
src/pytbox/utils/ping_checker.py
|
|
71
71
|
src/pytbox/utils/response.py
|
|
72
72
|
src/pytbox/utils/richutils.py
|
|
73
73
|
src/pytbox/utils/timeutils.py
|
|
74
74
|
src/pytbox/win/ad.py
|
|
75
75
|
tests/test_base.py
|
|
76
76
|
tests/test_feishu.py
|
|
77
|
+
tests/test_load_jsonfile.py
|
|
77
78
|
tests/test_logger.py
|
|
78
79
|
tests/test_onepassword_connect.py
|
|
79
80
|
tests/test_onepassword_sa.py
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from pytbox.utils.load_config import load_config_by_file
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
config = load_config_by_file(
|
|
8
|
+
path='/workspaces/pytbox/tests/alert/config_dev.toml',
|
|
9
|
+
# oc_vault_id="hcls5uxuq5dmxorw6rfewefdsa"
|
|
10
|
+
jsonfile="test_load_config.json"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
print(config['json_test'])
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from pytbox.base import get_logger_sls, get_logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
log = get_logger('tests.test_logger')
|
|
7
|
+
log_sls = get_logger_sls(app="tests.test_logger")
|
|
8
|
+
|
|
9
|
+
def test_logger_info():
|
|
10
|
+
log.info('test_logger_info')
|
|
11
|
+
log.error('test error log2')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_logger_sls():
|
|
15
|
+
log_sls.debug('test_logger_sls')
|
|
16
|
+
# log.error('test error log2')
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
# test_logger_info()
|
|
20
|
+
test_logger_sls()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from pytbox.base import vm, get_cronjob_counter
|
|
4
|
+
from pytbox.utils.cronjob import cronjob_counter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_query():
|
|
8
|
+
r = vm.query('ping_average_response_ms')
|
|
9
|
+
print(r)
|
|
10
|
+
|
|
11
|
+
def test_check_ping_result():
|
|
12
|
+
r = vm.check_ping_result(target='121.46.237.186', last_minute=10)
|
|
13
|
+
print(r)
|
|
14
|
+
|
|
15
|
+
def test_get_labels():
|
|
16
|
+
r = vm.get_labels('ping_average_response_ms')
|
|
17
|
+
print(r)
|
|
18
|
+
|
|
19
|
+
@get_cronjob_counter(app_type="tests", app="tests.test_victoriameterics", schedule_interval='5s')
|
|
20
|
+
def test_check_snmp_port_status():
|
|
21
|
+
r = vm.check_snmp_port_status(sysname="shylf-prod-coresw-ce6820-182", if_name="10GE1/0/47", last_minute=10)
|
|
22
|
+
print(r)
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
# test_get_labels()
|
|
26
|
+
# test_query()
|
|
27
|
+
# test_check_ping_result()
|
|
28
|
+
test_check_snmp_port_status()
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from typing import Literal, Optional
|
|
5
|
-
import requests
|
|
6
|
-
from ..utils.response import ReturnResponse
|
|
7
|
-
from ..utils.load_vm_devfile import load_dev_file
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class VictoriaMetrics:
|
|
11
|
-
|
|
12
|
-
def __init__(self, url: str='', timeout: int=3) -> None:
|
|
13
|
-
self.url = url
|
|
14
|
-
self.timeout = timeout
|
|
15
|
-
self.session = requests.Session()
|
|
16
|
-
self.session.headers.update({
|
|
17
|
-
'Content-Type': 'application/json',
|
|
18
|
-
'Accept': 'application/json'
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
def query(self, query: str, output_format: Literal['json']=None) -> ReturnResponse:
|
|
22
|
-
'''
|
|
23
|
-
查询指标数据
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
query (str): 查询语句
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
dict: 查询结果
|
|
30
|
-
'''
|
|
31
|
-
url = f"{self.url}/prometheus/api/v1/query"
|
|
32
|
-
r = requests.get(
|
|
33
|
-
url,
|
|
34
|
-
timeout=self.timeout,
|
|
35
|
-
params={"query": query}
|
|
36
|
-
)
|
|
37
|
-
res_json = r.json()
|
|
38
|
-
status = res_json.get("status")
|
|
39
|
-
result = res_json.get("data", {}).get("result", [])
|
|
40
|
-
is_json = output_format == 'json'
|
|
41
|
-
|
|
42
|
-
if status == "success":
|
|
43
|
-
if result:
|
|
44
|
-
code = 0
|
|
45
|
-
msg = f"[{query}] 查询成功!"
|
|
46
|
-
data = result
|
|
47
|
-
else:
|
|
48
|
-
code = 2
|
|
49
|
-
msg = f"[{query}] 没有查询到结果"
|
|
50
|
-
data = res_json
|
|
51
|
-
else:
|
|
52
|
-
code = 1
|
|
53
|
-
msg = f"[{query}] 查询失败: {res_json.get('error')}"
|
|
54
|
-
data = res_json
|
|
55
|
-
|
|
56
|
-
resp = ReturnResponse(code=code, msg=msg, data=data)
|
|
57
|
-
|
|
58
|
-
if is_json:
|
|
59
|
-
json_result = json.dumps(resp.__dict__, ensure_ascii=False)
|
|
60
|
-
return json_result
|
|
61
|
-
else:
|
|
62
|
-
return resp
|
|
63
|
-
|
|
64
|
-
def get_labels(self, metric_name: str) -> ReturnResponse:
|
|
65
|
-
url = f"{self.url}/api/v1/series?match[]={metric_name}"
|
|
66
|
-
response = requests.get(url, timeout=self.timeout)
|
|
67
|
-
results = response.json()
|
|
68
|
-
if results['status'] == 'success':
|
|
69
|
-
return ReturnResponse(code=0, msg=f"metric name: {metric_name} 获取到 {len(results['data'])} 条数据", data=results['data'])
|
|
70
|
-
else:
|
|
71
|
-
return ReturnResponse(code=1, msg=f"metric name: {metric_name} 查询失败")
|
|
72
|
-
|
|
73
|
-
def check_ping_result(self, target: str, last_minute: int=10, env: str='prod', dev_file: str='') -> ReturnResponse:
|
|
74
|
-
'''
|
|
75
|
-
检查ping结果
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
target (str): 目标地址
|
|
79
|
-
last_minute (int, optional): 最近多少分钟. Defaults to 10.
|
|
80
|
-
env (str, optional): 环境. Defaults to 'prod'.
|
|
81
|
-
dev_file (str, optional): 开发文件. Defaults to ''.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
ReturnResponse:
|
|
85
|
-
code = 0 正常, code = 1 异常, code = 2 没有查询到数据, 建议将其判断为正常
|
|
86
|
-
'''
|
|
87
|
-
if target:
|
|
88
|
-
# 这里需要在字符串中保留 {},同时插入 target,可以用双大括号转义
|
|
89
|
-
query = f"ping_result_code{{target='{target}'}}"
|
|
90
|
-
else:
|
|
91
|
-
query = "ping_result_code"
|
|
92
|
-
|
|
93
|
-
if last_minute:
|
|
94
|
-
query = query + f"[{last_minute}m]"
|
|
95
|
-
|
|
96
|
-
if env == 'dev':
|
|
97
|
-
r = load_dev_file(dev_file)
|
|
98
|
-
else:
|
|
99
|
-
r = self.query(query=query)
|
|
100
|
-
|
|
101
|
-
if r.code == 0:
|
|
102
|
-
values = r.data[0]['values']
|
|
103
|
-
if len(values) == 2 and values[1] == "0":
|
|
104
|
-
code = 0
|
|
105
|
-
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
106
|
-
else:
|
|
107
|
-
|
|
108
|
-
if all(str(item[1]) == "1" for item in values):
|
|
109
|
-
code = 1
|
|
110
|
-
msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
|
|
111
|
-
else:
|
|
112
|
-
code = 0
|
|
113
|
-
msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
|
|
114
|
-
elif r.code == 2:
|
|
115
|
-
code = 2
|
|
116
|
-
msg = f"没有查询到 {target} 最近 {last_minute} 分钟的ping结果!"
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
data = r.data[0]
|
|
120
|
-
except KeyError:
|
|
121
|
-
data = r.data
|
|
122
|
-
|
|
123
|
-
return ReturnResponse(code=code, msg=msg, data=data)
|
|
124
|
-
|
|
125
|
-
def check_interface_rate(self,
|
|
126
|
-
direction: Literal['in', 'out'],
|
|
127
|
-
sysName: str,
|
|
128
|
-
ifName:str,
|
|
129
|
-
last_minutes: Optional[int] = None
|
|
130
|
-
) -> ReturnResponse:
|
|
131
|
-
"""查询指定设备的入方向总流量速率(bps)。
|
|
132
|
-
|
|
133
|
-
使用 PromQL 对 `snmp_interface_ifHCInOctets` 进行速率计算并聚合到设备级别,
|
|
134
|
-
将结果从字节每秒转换为比特每秒(乘以 8)。
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
sysName: 设备 `sysName` 标签值。
|
|
138
|
-
last_minutes: 计算速率的时间窗口(分钟)。未提供时默认使用 5 分钟窗口。
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
ReturnResponse: 查询结果包装。
|
|
142
|
-
"""
|
|
143
|
-
if direction == 'in':
|
|
144
|
-
query = f'(rate(snmp_interface_ifHCInOctets{{sysName="{sysName}", ifName="{ifName}"}}[{last_minutes}m])) * 8 / 1000000'
|
|
145
|
-
else:
|
|
146
|
-
query = f'(rate(snmp_interface_ifHCOutOctets{{sysName="{sysName}", ifName="{ifName}"}}[{last_minutes}m])) * 8 / 1000000'
|
|
147
|
-
r = self.query(query)
|
|
148
|
-
rate = r.data[0]['value'][1]
|
|
149
|
-
return int(float(rate))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
from pytbox.base import get_logger
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
log = get_logger('tests.test_logger')
|
|
8
|
-
|
|
9
|
-
def test_logger_info():
|
|
10
|
-
log.info('test_logger_info')
|
|
11
|
-
log.error('test error log2')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if __name__ == "__main__":
|
|
15
|
-
test_logger_info()
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
from pytbox.base import vm
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_query():
|
|
8
|
-
r = vm.query('ping_average_response_ms')
|
|
9
|
-
print(r)
|
|
10
|
-
|
|
11
|
-
def test_check_ping_result():
|
|
12
|
-
r = vm.check_ping_result(target='121.46.237.186', last_minute=10)
|
|
13
|
-
print(r)
|
|
14
|
-
|
|
15
|
-
def test_get_labels():
|
|
16
|
-
r = vm.get_labels('ping_average_response_ms')
|
|
17
|
-
print(r)
|
|
18
|
-
|
|
19
|
-
if __name__ == "__main__":
|
|
20
|
-
# test_get_labels()
|
|
21
|
-
# test_query()
|
|
22
|
-
test_check_ping_result()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.http_response/http_response.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.net_response/net_response.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
{pytbox-0.1.5 → pytbox-0.1.7}/src/pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|