nginx-lens 0.3.2__py3-none-any.whl → 0.3.4__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.
Potentially problematic release.
This version of nginx-lens might be problematic. Click here for more details.
- commands/health.py +17 -4
- commands/resolve.py +13 -4
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/METADATA +1 -1
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/RECORD +9 -9
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/WHEEL +1 -1
- upstream_checker/checker.py +95 -33
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/entry_points.txt +0 -0
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {nginx_lens-0.3.2.dist-info → nginx_lens-0.3.4.dist-info}/top_level.txt +0 -0
commands/health.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.table import Table
|
|
@@ -13,31 +14,36 @@ def health(
|
|
|
13
14
|
retries: int = typer.Option(1, help="Количество попыток"),
|
|
14
15
|
mode: str = typer.Option("tcp", help="Режим проверки: tcp или http", case_sensitive=False),
|
|
15
16
|
resolve: bool = typer.Option(False, "--resolve", "-r", help="Показать резолвленные IP-адреса"),
|
|
17
|
+
max_workers: int = typer.Option(10, "--max-workers", "-w", help="Максимальное количество потоков для параллельной обработки"),
|
|
16
18
|
):
|
|
17
19
|
"""
|
|
18
20
|
Проверяет доступность upstream-серверов, определённых в nginx.conf. Выводит таблицу.
|
|
21
|
+
Использует параллельную обработку для ускорения проверки множества upstream серверов.
|
|
19
22
|
|
|
20
23
|
Пример:
|
|
21
24
|
nginx-lens health /etc/nginx/nginx.conf
|
|
22
25
|
nginx-lens health /etc/nginx/nginx.conf --timeout 5 --retries 3 --mode http
|
|
23
26
|
nginx-lens health /etc/nginx/nginx.conf --resolve
|
|
27
|
+
nginx-lens health /etc/nginx/nginx.conf --max-workers 20
|
|
24
28
|
"""
|
|
29
|
+
exit_code = 0
|
|
30
|
+
|
|
25
31
|
try:
|
|
26
32
|
tree = parse_nginx_config(config_path)
|
|
27
33
|
except FileNotFoundError:
|
|
28
34
|
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
29
|
-
|
|
35
|
+
sys.exit(1)
|
|
30
36
|
except Exception as e:
|
|
31
37
|
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
32
|
-
|
|
38
|
+
sys.exit(1)
|
|
33
39
|
|
|
34
40
|
upstreams = tree.get_upstreams()
|
|
35
|
-
results = check_upstreams(upstreams, timeout=timeout, retries=retries, mode=mode.lower())
|
|
41
|
+
results = check_upstreams(upstreams, timeout=timeout, retries=retries, mode=mode.lower(), max_workers=max_workers)
|
|
36
42
|
|
|
37
43
|
# Если нужно показать резолвленные IP-адреса
|
|
38
44
|
resolved_info = {}
|
|
39
45
|
if resolve:
|
|
40
|
-
resolved_info = resolve_upstreams(upstreams)
|
|
46
|
+
resolved_info = resolve_upstreams(upstreams, max_workers=max_workers)
|
|
41
47
|
|
|
42
48
|
table = Table(show_header=True, header_style="bold blue")
|
|
43
49
|
table.add_column("Address")
|
|
@@ -50,6 +56,10 @@ def health(
|
|
|
50
56
|
status = "Healthy" if srv["healthy"] else "Unhealthy"
|
|
51
57
|
color = "green" if srv["healthy"] else "red"
|
|
52
58
|
|
|
59
|
+
# Проверяем статус здоровья
|
|
60
|
+
if not srv["healthy"]:
|
|
61
|
+
exit_code = 1
|
|
62
|
+
|
|
53
63
|
if resolve:
|
|
54
64
|
resolved_list = []
|
|
55
65
|
if name in resolved_info:
|
|
@@ -64,11 +74,14 @@ def health(
|
|
|
64
74
|
# Если есть "invalid resolve", показываем красным, иначе зеленым
|
|
65
75
|
if any("invalid resolve" in r for r in resolved_list):
|
|
66
76
|
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[red]{resolved_str}[/red]")
|
|
77
|
+
exit_code = 1
|
|
67
78
|
else:
|
|
68
79
|
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", f"[green]{resolved_str}[/green]")
|
|
69
80
|
else:
|
|
70
81
|
table.add_row(srv["address"], f"[{color}]{status}[/{color}]", "[yellow]Failed to resolve[/yellow]")
|
|
82
|
+
exit_code = 1
|
|
71
83
|
else:
|
|
72
84
|
table.add_row(srv["address"], f"[{color}]{status}[/{color}]")
|
|
73
85
|
|
|
74
86
|
console.print(table)
|
|
87
|
+
sys.exit(exit_code)
|
commands/resolve.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import typer
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
from rich.table import Table
|
|
@@ -9,28 +10,33 @@ console = Console()
|
|
|
9
10
|
|
|
10
11
|
def resolve(
|
|
11
12
|
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
13
|
+
max_workers: int = typer.Option(10, "--max-workers", "-w", help="Максимальное количество потоков для параллельной обработки"),
|
|
12
14
|
):
|
|
13
15
|
"""
|
|
14
16
|
Резолвит DNS имена upstream-серверов в IP-адреса.
|
|
17
|
+
Использует параллельную обработку для ускорения резолвинга множества upstream серверов.
|
|
15
18
|
|
|
16
19
|
Пример:
|
|
17
20
|
nginx-lens resolve /etc/nginx/nginx.conf
|
|
21
|
+
nginx-lens resolve /etc/nginx/nginx.conf --max-workers 20
|
|
18
22
|
"""
|
|
23
|
+
exit_code = 0
|
|
24
|
+
|
|
19
25
|
try:
|
|
20
26
|
tree = parse_nginx_config(config_path)
|
|
21
27
|
except FileNotFoundError:
|
|
22
28
|
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
23
|
-
|
|
29
|
+
sys.exit(1)
|
|
24
30
|
except Exception as e:
|
|
25
31
|
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
26
|
-
|
|
32
|
+
sys.exit(1)
|
|
27
33
|
|
|
28
34
|
upstreams = tree.get_upstreams()
|
|
29
35
|
if not upstreams:
|
|
30
36
|
console.print("[yellow]Не найдено ни одного upstream в конфигурации.[/yellow]")
|
|
31
|
-
|
|
37
|
+
sys.exit(0) # Нет upstream - это не ошибка, просто нет чего проверять
|
|
32
38
|
|
|
33
|
-
results = resolve_upstreams(upstreams)
|
|
39
|
+
results = resolve_upstreams(upstreams, max_workers=max_workers)
|
|
34
40
|
|
|
35
41
|
table = Table(show_header=True, header_style="bold blue")
|
|
36
42
|
table.add_column("Upstream Name")
|
|
@@ -47,10 +53,13 @@ def resolve(
|
|
|
47
53
|
# Если есть "invalid resolve", показываем красным, иначе зеленым
|
|
48
54
|
if any("invalid resolve" in r for r in resolved_list):
|
|
49
55
|
table.add_row(upstream_name, srv["address"], f"[red]{resolved_str}[/red]")
|
|
56
|
+
exit_code = 1
|
|
50
57
|
else:
|
|
51
58
|
table.add_row(upstream_name, srv["address"], f"[green]{resolved_str}[/green]")
|
|
52
59
|
else:
|
|
53
60
|
table.add_row(upstream_name, srv["address"], "[red]Failed to resolve[/red]")
|
|
61
|
+
exit_code = 1
|
|
54
62
|
|
|
55
63
|
console.print(table)
|
|
64
|
+
sys.exit(exit_code)
|
|
56
65
|
|
|
@@ -15,10 +15,10 @@ commands/analyze.py,sha256=W6begSgXNjgKJGoGeguR3WKgHPLkClWXxxpDcqvsJdc,8343
|
|
|
15
15
|
commands/cli.py,sha256=brzp6xDDWIrm7ibaoT4x94hgAdBB2DVWniXoK8dRylE,782
|
|
16
16
|
commands/diff.py,sha256=C7gRIWh6DNWHzjiQBPVTn-rZ40m2KCY75Zd6Q4URJIE,2076
|
|
17
17
|
commands/graph.py,sha256=xB6KjXBkLmm5gII3e-5BMRGO7WeTwc7EFxRGzYgnme4,5947
|
|
18
|
-
commands/health.py,sha256=
|
|
18
|
+
commands/health.py,sha256=lxrvuD-ClMLpTJ7gs7kn50fXwA4uBlHMPWA1ZxdEtAE,4167
|
|
19
19
|
commands/include.py,sha256=5PTYG5C00-AlWfIgpQXLq9E7C9yTFSv7HrZkM5ogDps,2224
|
|
20
20
|
commands/logs.py,sha256=RkPUdIpbO9dOVL56lemreYRuAjMjcqqMxRCKOFv2gC4,3691
|
|
21
|
-
commands/resolve.py,sha256=
|
|
21
|
+
commands/resolve.py,sha256=MRruIH46tIelUyyrdrF70ai-tluuEJ13Jcj2nyRCSPA,2820
|
|
22
22
|
commands/route.py,sha256=-x_71u6ENl3iO-oxK3bdE8v5eZKf4xRCydeUyXMFVrY,3163
|
|
23
23
|
commands/syntax.py,sha256=ZWFdaL8LVv9S694wlk2aV3HJKb0OSKjw3wNgTlNvFR8,3418
|
|
24
24
|
commands/tree.py,sha256=mDfx0Aeg1EDQSYQoJ2nJIkSd_uP7ZR7pEqy7Cw3clQ0,2161
|
|
@@ -26,13 +26,13 @@ exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
26
26
|
exporter/graph.py,sha256=WYUrqUgCaK6KihgxAcRHaQn4oMo6b7ybC8yb_36ZIsA,3995
|
|
27
27
|
exporter/html.py,sha256=uquEM-WvBt2aV9GshgaI3UVhYd8sD0QQ-OmuNtvYUdU,798
|
|
28
28
|
exporter/markdown.py,sha256=_0mXQIhurGEZ0dO-eq9DbsuKNrgEDIblgtL3DAgYNo8,724
|
|
29
|
-
nginx_lens-0.3.
|
|
29
|
+
nginx_lens-0.3.4.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
|
|
30
30
|
parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
parser/nginx_parser.py,sha256=Sa9FtGAkvTqNzoehBvgLUWPJHLLIZYWH9ugSHW50X8s,3699
|
|
32
32
|
upstream_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
-
upstream_checker/checker.py,sha256=
|
|
34
|
-
nginx_lens-0.3.
|
|
35
|
-
nginx_lens-0.3.
|
|
36
|
-
nginx_lens-0.3.
|
|
37
|
-
nginx_lens-0.3.
|
|
38
|
-
nginx_lens-0.3.
|
|
33
|
+
upstream_checker/checker.py,sha256=i3L6XqUHUH5hcyLq5PXx6wOyjzEL_Z7xYCA3FffFOrU,11257
|
|
34
|
+
nginx_lens-0.3.4.dist-info/METADATA,sha256=E7ce-3zNXvNVp7-y_TvlnmtCTIjMhid2sdqzbrpgy8U,552
|
|
35
|
+
nginx_lens-0.3.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
36
|
+
nginx_lens-0.3.4.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
|
|
37
|
+
nginx_lens-0.3.4.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
|
|
38
|
+
nginx_lens-0.3.4.dist-info/RECORD,,
|
upstream_checker/checker.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import socket
|
|
4
4
|
import time
|
|
5
5
|
import http.client
|
|
6
|
-
from typing import Dict, List
|
|
6
|
+
from typing import Dict, List, Tuple
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
8
|
try:
|
|
8
9
|
import dns.resolver
|
|
9
10
|
import dns.exception
|
|
@@ -80,14 +81,12 @@ def resolve_address(address: str) -> List[str]:
|
|
|
80
81
|
return []
|
|
81
82
|
host, port = parts
|
|
82
83
|
|
|
83
|
-
# Если это уже IPv4 адрес, возвращаем как есть
|
|
84
84
|
try:
|
|
85
85
|
socket.inet_aton(host)
|
|
86
86
|
return [host_port]
|
|
87
87
|
except socket.error:
|
|
88
88
|
pass
|
|
89
89
|
|
|
90
|
-
# Проверяем IPv6 (в квадратных скобках)
|
|
91
90
|
if host.startswith("[") and host.endswith("]"):
|
|
92
91
|
ipv6_host = host[1:-1]
|
|
93
92
|
try:
|
|
@@ -96,11 +95,9 @@ def resolve_address(address: str) -> List[str]:
|
|
|
96
95
|
except (socket.error, OSError):
|
|
97
96
|
pass
|
|
98
97
|
|
|
99
|
-
# Используем dnspython для детального DNS резолвинга
|
|
100
98
|
if DNS_AVAILABLE:
|
|
101
99
|
return _resolve_with_dns(host, port)
|
|
102
100
|
else:
|
|
103
|
-
# Fallback на стандартный socket, если dnspython недоступен
|
|
104
101
|
return _resolve_with_socket(host, port)
|
|
105
102
|
except (ValueError, IndexError, AttributeError):
|
|
106
103
|
return []
|
|
@@ -112,25 +109,21 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
|
|
|
112
109
|
cname_info = None
|
|
113
110
|
invalid_type = None
|
|
114
111
|
|
|
115
|
-
# Сначала проверяем CNAME для исходного хоста
|
|
116
112
|
try:
|
|
117
113
|
cname_answer = dns.resolver.resolve(host, 'CNAME', raise_on_no_answer=False)
|
|
118
114
|
if cname_answer:
|
|
119
115
|
cname_target = str(cname_answer[0].target).rstrip('.')
|
|
120
116
|
cname_info = cname_target
|
|
121
117
|
|
|
122
|
-
# Если есть CNAME, проверяем A записи для CNAME цели (не для исходного хоста)
|
|
123
118
|
try:
|
|
124
119
|
a_answer = dns.resolver.resolve(cname_target, 'A', raise_on_no_answer=False)
|
|
125
120
|
if a_answer:
|
|
126
|
-
# CNAME ведет на валидные A записи
|
|
127
121
|
resolved_ips = []
|
|
128
122
|
for rdata in a_answer:
|
|
129
123
|
ip = str(rdata.address)
|
|
130
124
|
resolved_ips.append(f"{ip}:{port} (via {cname_info})")
|
|
131
125
|
return resolved_ips
|
|
132
126
|
else:
|
|
133
|
-
# Нет A записей для CNAME цели, проверяем другие типы
|
|
134
127
|
try:
|
|
135
128
|
txt_answer = dns.resolver.resolve(cname_target, 'TXT', raise_on_no_answer=False)
|
|
136
129
|
if txt_answer:
|
|
@@ -145,7 +138,6 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
|
|
|
145
138
|
except:
|
|
146
139
|
pass
|
|
147
140
|
if not invalid_type:
|
|
148
|
-
# Проверяем другие невалидные типы
|
|
149
141
|
try:
|
|
150
142
|
ns_answer = dns.resolver.resolve(cname_target, 'NS', raise_on_no_answer=False)
|
|
151
143
|
if ns_answer:
|
|
@@ -161,7 +153,6 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
|
|
|
161
153
|
except Exception:
|
|
162
154
|
pass
|
|
163
155
|
|
|
164
|
-
# Если нет CNAME, получаем A записи для исходного хоста
|
|
165
156
|
try:
|
|
166
157
|
a_answer = dns.resolver.resolve(host, 'A', raise_on_no_answer=False)
|
|
167
158
|
if a_answer:
|
|
@@ -181,13 +172,10 @@ def _resolve_with_dns(host: str, port: str) -> List[str]:
|
|
|
181
172
|
def _resolve_with_socket(host: str, port: str) -> List[str]:
|
|
182
173
|
"""Fallback резолвинг через socket (без информации о CNAME)."""
|
|
183
174
|
try:
|
|
184
|
-
# gethostbyname_ex возвращает (hostname, aliaslist, ipaddrlist)
|
|
185
175
|
_, _, ipaddrlist = socket.gethostbyname_ex(host)
|
|
186
|
-
# Фильтруем только IPv4 адреса (IPv6 обрабатываются отдельно)
|
|
187
176
|
resolved_ips = []
|
|
188
177
|
for ip in ipaddrlist:
|
|
189
178
|
try:
|
|
190
|
-
# Проверяем, что это IPv4
|
|
191
179
|
socket.inet_aton(ip)
|
|
192
180
|
resolved_ips.append(f"{ip}:{port}")
|
|
193
181
|
except socket.error:
|
|
@@ -198,11 +186,16 @@ def _resolve_with_socket(host: str, port: str) -> List[str]:
|
|
|
198
186
|
|
|
199
187
|
|
|
200
188
|
def resolve_upstreams(
|
|
201
|
-
upstreams: Dict[str, List[str]]
|
|
189
|
+
upstreams: Dict[str, List[str]],
|
|
190
|
+
max_workers: int = 10
|
|
202
191
|
) -> Dict[str, List[dict]]:
|
|
203
192
|
"""
|
|
204
193
|
Резолвит DNS имена upstream-серверов в IP-адреса.
|
|
205
194
|
|
|
195
|
+
Args:
|
|
196
|
+
upstreams: Словарь upstream серверов
|
|
197
|
+
max_workers: Максимальное количество потоков для параллельной обработки
|
|
198
|
+
|
|
206
199
|
Возвращает:
|
|
207
200
|
{
|
|
208
201
|
"backend": [
|
|
@@ -213,27 +206,71 @@ def resolve_upstreams(
|
|
|
213
206
|
]
|
|
214
207
|
}
|
|
215
208
|
"""
|
|
216
|
-
|
|
209
|
+
# Собираем все задачи для параллельной обработки
|
|
210
|
+
tasks = []
|
|
211
|
+
task_to_key = {}
|
|
212
|
+
|
|
217
213
|
for name, servers in upstreams.items():
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
214
|
+
for idx, srv in enumerate(servers):
|
|
215
|
+
key = (name, idx, srv)
|
|
216
|
+
tasks.append((key, srv))
|
|
217
|
+
task_to_key[key] = (name, idx)
|
|
218
|
+
|
|
219
|
+
results = {}
|
|
220
|
+
for name in upstreams.keys():
|
|
221
|
+
results[name] = [None] * len(upstreams[name])
|
|
222
|
+
|
|
223
|
+
# Если нет задач, возвращаем пустой результат
|
|
224
|
+
if not tasks:
|
|
225
|
+
return results
|
|
226
|
+
|
|
227
|
+
# Параллельная обработка резолвинга
|
|
228
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
229
|
+
future_to_key = {executor.submit(resolve_address, srv): key for key, srv in tasks}
|
|
230
|
+
|
|
231
|
+
for future in as_completed(future_to_key):
|
|
232
|
+
key = future_to_key[future]
|
|
233
|
+
name, idx = task_to_key[key]
|
|
234
|
+
try:
|
|
235
|
+
resolved = future.result()
|
|
236
|
+
results[name][idx] = {
|
|
237
|
+
"address": key[2],
|
|
238
|
+
"resolved": resolved
|
|
239
|
+
}
|
|
240
|
+
except Exception:
|
|
241
|
+
results[name][idx] = {
|
|
242
|
+
"address": key[2],
|
|
243
|
+
"resolved": []
|
|
244
|
+
}
|
|
245
|
+
|
|
225
246
|
return results
|
|
226
247
|
|
|
227
248
|
|
|
249
|
+
def _check_single_upstream(srv: str, timeout: float, retries: int, mode: str) -> Tuple[str, bool]:
|
|
250
|
+
"""Вспомогательная функция для проверки одного upstream сервера."""
|
|
251
|
+
if mode.lower() == "http":
|
|
252
|
+
healthy = check_http(srv, timeout, retries)
|
|
253
|
+
else:
|
|
254
|
+
healthy = check_tcp(srv, timeout, retries)
|
|
255
|
+
return (srv, healthy)
|
|
256
|
+
|
|
257
|
+
|
|
228
258
|
def check_upstreams(
|
|
229
259
|
upstreams: Dict[str, List[str]],
|
|
230
260
|
timeout: float = 2.0,
|
|
231
261
|
retries: int = 1,
|
|
232
|
-
mode: str = "tcp"
|
|
262
|
+
mode: str = "tcp",
|
|
263
|
+
max_workers: int = 10
|
|
233
264
|
) -> Dict[str, List[dict]]:
|
|
234
265
|
"""
|
|
235
266
|
Проверяет доступность upstream-серверов.
|
|
236
|
-
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
upstreams: Словарь upstream серверов
|
|
270
|
+
timeout: Таймаут проверки (сек)
|
|
271
|
+
retries: Количество попыток
|
|
272
|
+
mode: "tcp" (по умолчанию) или "http"
|
|
273
|
+
max_workers: Максимальное количество потоков для параллельной обработки
|
|
237
274
|
|
|
238
275
|
Возвращает:
|
|
239
276
|
{
|
|
@@ -243,13 +280,38 @@ def check_upstreams(
|
|
|
243
280
|
]
|
|
244
281
|
}
|
|
245
282
|
"""
|
|
246
|
-
|
|
283
|
+
# Собираем все задачи для параллельной обработки
|
|
284
|
+
tasks = []
|
|
285
|
+
task_to_key = {}
|
|
286
|
+
|
|
247
287
|
for name, servers in upstreams.items():
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
288
|
+
for idx, srv in enumerate(servers):
|
|
289
|
+
key = (name, idx, srv)
|
|
290
|
+
tasks.append((key, srv))
|
|
291
|
+
task_to_key[key] = (name, idx)
|
|
292
|
+
|
|
293
|
+
results = {}
|
|
294
|
+
for name in upstreams.keys():
|
|
295
|
+
results[name] = [None] * len(upstreams[name])
|
|
296
|
+
|
|
297
|
+
# Если нет задач, возвращаем пустой результат
|
|
298
|
+
if not tasks:
|
|
299
|
+
return results
|
|
300
|
+
|
|
301
|
+
# Параллельная обработка проверок
|
|
302
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
303
|
+
future_to_key = {
|
|
304
|
+
executor.submit(_check_single_upstream, srv, timeout, retries, mode): key
|
|
305
|
+
for key, srv in tasks
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for future in as_completed(future_to_key):
|
|
309
|
+
key = future_to_key[future]
|
|
310
|
+
name, idx = task_to_key[key]
|
|
311
|
+
try:
|
|
312
|
+
srv, healthy = future.result()
|
|
313
|
+
results[name][idx] = {"address": srv, "healthy": healthy}
|
|
314
|
+
except Exception:
|
|
315
|
+
results[name][idx] = {"address": key[2], "healthy": False}
|
|
316
|
+
|
|
255
317
|
return results
|
|
File without changes
|
|
File without changes
|
|
File without changes
|