proxy-aiops 0.3.0__tar.gz → 0.3.1__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.
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/PKG-INFO +1 -1
- proxy_aiops-0.3.1/RELEASE_NOTES.md +26 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/docs/VERIFICATION.md +8 -4
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/connection.py +23 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/platform.py +18 -13
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/pyproject.toml +1 -1
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/server.json +2 -2
- proxy_aiops-0.3.1/tests/test_dpapi_version.py +56 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_platform.py +4 -2
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/uv.lock +1 -1
- proxy_aiops-0.3.0/RELEASE_NOTES.md +0 -27
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/.github/workflows/mcp-publish.yml +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/.github/workflows/publish.yml +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/.gitignore +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/CHANGELOG.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/LICENSE +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/README.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/SECURITY.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/_shared.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/server.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/analysis.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/certs.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/configread.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/routes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/services.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/status.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/traffic.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/undo.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/mcp_server/tools/writes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/_common.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/_root.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/analyze.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/certs.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/configcmd.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/doctor.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/init.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/overview.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/routes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/secret.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/server.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/services.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/cli/undo.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/config.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/doctor.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/audit.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/budget.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/decorators.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/paths.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/patterns.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/policy.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/readonly.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/sanitize.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/governance/undo.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/__init__.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/_util.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/analysis.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/certs.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/configread.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/overview.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/routes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/services.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/status.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/traffic.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/ops/writes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/proxy_aiops/secretstore.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/skills/proxy-aiops/SKILL.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/skills/proxy-aiops/references/agent-guardrails.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/skills/proxy-aiops/references/capabilities.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/skills/proxy-aiops/references/cli-reference.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/skills/proxy-aiops/references/setup-guide.md +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/smithery.yaml +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/conftest.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_analysis.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_cli_reads.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_cli_secret.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_cli_writes.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_connection.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_doctor.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_gov_audit.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_gov_decorators.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_gov_patterns.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_gov_policy.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_governance_persistence.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_init.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_int_fidelity.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_mcp_read_tools.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_ops_extra.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_optional_fields.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_readonly.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_reads.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_secretstore.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_smoke.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_undo_executor.py +0 -0
- {proxy_aiops-0.3.0 → proxy_aiops-0.3.1}/tests/test_writes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-aiops
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Governed AI-ops for Traefik + Caddy + HAProxy reverse proxies / load balancers: routes, services, upstream health, TLS cert expiry sweep, 5xx error-rate RCA, route conflict analysis, and governed writes (caddy config, haproxy server state/weight) with a built-in governance harness (audit, budget, undo, risk tiers)
|
|
5
5
|
Author-email: wei <zhouwei008@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Release notes — proxy-aiops 0.3.1
|
|
2
|
+
|
|
3
|
+
Previous release: 0.3.0.
|
|
4
|
+
|
|
5
|
+
## Fixed: the HAProxy branch could not talk to any current HAProxy
|
|
6
|
+
|
|
7
|
+
Every HAProxy path was hardcoded to Data Plane API **v2**. HAProxy 3.x ships
|
|
8
|
+
Data Plane API v3, which serves **only** `/v3` — every `/v2` path returns 404.
|
|
9
|
+
So the whole HAProxy platform branch was unusable against a current HAProxy,
|
|
10
|
+
failing at the very first probe.
|
|
11
|
+
|
|
12
|
+
The path registry now holds v3 paths, and the connection probes `/v3/info` once
|
|
13
|
+
per connection and rewrites the prefix to `/v2` when it sees an older server.
|
|
14
|
+
Both generations work; the probe is cached, so a v3 server costs one extra
|
|
15
|
+
request per connection and a v2 server two.
|
|
16
|
+
|
|
17
|
+
## Live-verified: HAProxy
|
|
18
|
+
|
|
19
|
+
Verified against **HAProxy 3.0.25 with Data Plane API v3.0.21**: `doctor`, plus
|
|
20
|
+
reads cross-checked against the Data Plane API itself — the configured backend
|
|
21
|
+
and its two servers were reported accurately, including `serversUp: 0` for
|
|
22
|
+
servers pointed at closed ports.
|
|
23
|
+
|
|
24
|
+
With Traefik 3.2.5 and Caddy 2 verified in 0.3.0, **all three platforms are now
|
|
25
|
+
live-verified**. See [docs/VERIFICATION.md](docs/VERIFICATION.md) — guarded
|
|
26
|
+
config writes and TLS/certificate expiry against real certificates remain open.
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
This document records what has and has not been validated against real reverse
|
|
4
4
|
proxies, so the maturity claim is auditable rather than a vibe.
|
|
5
5
|
|
|
6
|
-
## Already live-verified ✅ —
|
|
6
|
+
## Already live-verified ✅ — all three platforms (2026-07-20)
|
|
7
|
+
|
|
8
|
+
Traefik 3.2.5, Caddy 2, and HAProxy 3.0.25 (Data Plane API v3.0.21).
|
|
7
9
|
|
|
8
10
|
- `doctor` against both live endpoints (unauthenticated admin APIs, as the tool
|
|
9
11
|
documents for traefik/caddy).
|
|
@@ -22,9 +24,11 @@ that never round-trips through float64.
|
|
|
22
24
|
|
|
23
25
|
## Not yet live-verified ⚠️
|
|
24
26
|
|
|
25
|
-
- **HAProxy
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
- **HAProxy is now verified** — and that run found the branch was **entirely
|
|
28
|
+
broken**: every path was hardcoded to Data Plane API v2, but HAProxy 3.x serves
|
|
29
|
+
only `/v3`, so the first probe 404'd. The connection now detects the API
|
|
30
|
+
generation and supports both. Still untested there: runtime server state changes
|
|
31
|
+
and the stats-derived traffic analyses under real load.
|
|
28
32
|
- **TLS / certificate expiry** (`certs`) against real certificates — both verified
|
|
29
33
|
instances served plaintext on a lab port.
|
|
30
34
|
- **Guarded config writes** (`config set/delete`) and their undo paths.
|
|
@@ -84,6 +84,8 @@ class ProxyConnection:
|
|
|
84
84
|
auth=self._build_auth(target),
|
|
85
85
|
headers={"Accept": "application/json"},
|
|
86
86
|
)
|
|
87
|
+
# HAProxy Data Plane API generation, resolved lazily by _haproxy_path.
|
|
88
|
+
self._dpapi_prefix: str | None = None
|
|
87
89
|
|
|
88
90
|
@staticmethod
|
|
89
91
|
def _build_auth(target: TargetConfig) -> Any | None:
|
|
@@ -104,8 +106,29 @@ class ProxyConnection:
|
|
|
104
106
|
def platform(self) -> Any:
|
|
105
107
|
return self._target.platform_obj
|
|
106
108
|
|
|
109
|
+
def _haproxy_path(self, path: str) -> str:
|
|
110
|
+
"""Rewrite a ``/v3/`` Data Plane API path to ``/v2/`` on an older server.
|
|
111
|
+
|
|
112
|
+
HAProxy 3.x ships Data Plane API v3, which serves **only** ``/v3`` — every
|
|
113
|
+
``/v2`` path 404s. Older installs are the mirror image. The registry holds
|
|
114
|
+
the current (v3) paths and this probes ``/v3/info`` once per connection,
|
|
115
|
+
falling back to ``/v2`` so both generations work. Probed lazily and cached:
|
|
116
|
+
a v3 server costs one extra request per connection, a v2 server two.
|
|
117
|
+
"""
|
|
118
|
+
if not path.startswith("/v3/"):
|
|
119
|
+
return path
|
|
120
|
+
if self._dpapi_prefix is None:
|
|
121
|
+
try:
|
|
122
|
+
resp = self._client.request("GET", "/v3/info")
|
|
123
|
+
self._dpapi_prefix = "/v3/" if resp.status_code < 400 else "/v2/"
|
|
124
|
+
except httpx.HTTPError:
|
|
125
|
+
self._dpapi_prefix = "/v3/" # unreachable: let the real call report it
|
|
126
|
+
return self._dpapi_prefix + path[4:]
|
|
127
|
+
|
|
107
128
|
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
108
129
|
label = self._target.platform_obj.label
|
|
130
|
+
if self._target.platform == "haproxy":
|
|
131
|
+
path = self._haproxy_path(path)
|
|
109
132
|
try:
|
|
110
133
|
resp = self._client.request(method, path, **kwargs)
|
|
111
134
|
except httpx.HTTPError as exc:
|
|
@@ -18,7 +18,7 @@ v0.1 registers three platforms:
|
|
|
18
18
|
read via ``GET /config/``, targeted config writes via ``PATCH/POST/DELETE
|
|
19
19
|
/config/<path>`` and full replace via ``POST /load``. This platform carries
|
|
20
20
|
the write surface (prior-config capture → replayable undo).
|
|
21
|
-
* **haproxy** — HAProxy Data Plane API v2 (``/
|
|
21
|
+
* **haproxy** — HAProxy Data Plane API v3, with v2 fallback (``/v3/services/haproxy/...``,
|
|
22
22
|
HTTP Basic auth): frontends/backends/servers/stats reads plus runtime
|
|
23
23
|
server writes (admin state ready/drain/maint, weight).
|
|
24
24
|
|
|
@@ -288,19 +288,24 @@ _CADDY_UNSUPPORTED = {
|
|
|
288
288
|
),
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
# ─── HAProxy (Data Plane API
|
|
291
|
+
# ─── HAProxy (Data Plane API, HTTP Basic auth) ──────────────────────────────
|
|
292
|
+
# Paths are written for Data Plane API **v3** (HAProxy 3.x). A v2 server is
|
|
293
|
+
# still supported: the connection probes once and rewrites the /v3/ prefix to
|
|
294
|
+
# /v2/ when it sees an older API. Verified live against dataplaneapi v3.0.21,
|
|
295
|
+
# which serves ONLY /v3 — every /v2 path 404s, so hardcoding v2 (as this did)
|
|
296
|
+
# made the whole HAProxy branch unusable on any current HAProxy.
|
|
292
297
|
_HAPROXY_PATHS = {
|
|
293
|
-
"probe": "/
|
|
294
|
-
"version": "/
|
|
295
|
-
"frontends": "/
|
|
296
|
-
"frontend_detail": "/
|
|
297
|
-
"binds": "/
|
|
298
|
-
"backends": "/
|
|
299
|
-
"backend_detail": "/
|
|
300
|
-
"servers": "/
|
|
301
|
-
"runtime_servers": "/
|
|
302
|
-
"runtime_server": "/
|
|
303
|
-
"stats": "/
|
|
298
|
+
"probe": "/v3/info",
|
|
299
|
+
"version": "/v3/info",
|
|
300
|
+
"frontends": "/v3/services/haproxy/configuration/frontends",
|
|
301
|
+
"frontend_detail": "/v3/services/haproxy/configuration/frontends/{name}",
|
|
302
|
+
"binds": "/v3/services/haproxy/configuration/binds?frontend={frontend}",
|
|
303
|
+
"backends": "/v3/services/haproxy/configuration/backends",
|
|
304
|
+
"backend_detail": "/v3/services/haproxy/configuration/backends/{name}",
|
|
305
|
+
"servers": "/v3/services/haproxy/configuration/servers?backend={backend}",
|
|
306
|
+
"runtime_servers": "/v3/services/haproxy/runtime/servers?backend={backend}",
|
|
307
|
+
"runtime_server": "/v3/services/haproxy/runtime/servers/{name}?backend={backend}",
|
|
308
|
+
"stats": "/v3/services/haproxy/stats/native",
|
|
304
309
|
}
|
|
305
310
|
_HAPROXY_UNSUPPORTED = {
|
|
306
311
|
"metrics": (
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxy-aiops"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Governed AI-ops for Traefik + Caddy + HAProxy reverse proxies / load balancers: routes, services, upstream health, TLS cert expiry sweep, 5xx error-rate RCA, route conflict analysis, and governed writes (caddy config, haproxy server state/weight) with a built-in governance harness (audit, budget, undo, risk tiers)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/AIops-tools/Proxy-AIops",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "proxy-aiops",
|
|
15
|
-
"version": "0.3.
|
|
15
|
+
"version": "0.3.1",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""HAProxy Data Plane API generation must be detected, not assumed.
|
|
2
|
+
|
|
3
|
+
Regression from live verification against HAProxy 3.0 / dataplaneapi v3.0.21,
|
|
4
|
+
which serves ONLY /v3 — every /v2 path 404s. The registry previously hardcoded
|
|
5
|
+
/v2, so the entire HAProxy branch was unusable against any current HAProxy.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from unittest.mock import MagicMock
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from proxy_aiops.connection import ProxyConnection
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _conn(probe_status: int) -> ProxyConnection:
|
|
18
|
+
c = ProxyConnection.__new__(ProxyConnection)
|
|
19
|
+
client = MagicMock(name="client")
|
|
20
|
+
client.request.return_value = MagicMock(status_code=probe_status)
|
|
21
|
+
c._client = client
|
|
22
|
+
c._dpapi_prefix = None
|
|
23
|
+
return c
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.unit
|
|
27
|
+
def test_v3_server_keeps_v3_paths():
|
|
28
|
+
c = _conn(200)
|
|
29
|
+
assert c._haproxy_path("/v3/info") == "/v3/info"
|
|
30
|
+
assert c._haproxy_path("/v3/services/haproxy/configuration/backends") == (
|
|
31
|
+
"/v3/services/haproxy/configuration/backends"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.unit
|
|
36
|
+
def test_v2_server_gets_paths_rewritten():
|
|
37
|
+
c = _conn(404) # /v3/info missing => an older Data Plane API
|
|
38
|
+
assert c._haproxy_path("/v3/info") == "/v2/info"
|
|
39
|
+
assert c._haproxy_path("/v3/services/haproxy/configuration/backends") == (
|
|
40
|
+
"/v2/services/haproxy/configuration/backends"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.unit
|
|
45
|
+
def test_probe_is_cached_not_repeated_per_request():
|
|
46
|
+
c = _conn(200)
|
|
47
|
+
for _ in range(5):
|
|
48
|
+
c._haproxy_path("/v3/info")
|
|
49
|
+
assert c._client.request.call_count == 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.unit
|
|
53
|
+
def test_non_versioned_paths_are_untouched():
|
|
54
|
+
c = _conn(200)
|
|
55
|
+
assert c._haproxy_path("/metrics") == "/metrics"
|
|
56
|
+
assert c._client.request.call_count == 0, "must not probe for unrelated paths"
|
|
@@ -44,9 +44,11 @@ def test_path_templates_differ_per_platform():
|
|
|
44
44
|
ha = get_platform(HAPROXY)
|
|
45
45
|
assert tr.path("routers") == "/api/http/routers"
|
|
46
46
|
assert tr.path("router_detail", name="web@file").endswith("/web%40file")
|
|
47
|
-
|
|
47
|
+
# The registry holds Data Plane API **v3** paths (HAProxy 3.x serves only
|
|
48
|
+
# /v3); the connection rewrites them to /v2 for an older server.
|
|
49
|
+
assert ha.path("backends") == "/v3/services/haproxy/configuration/backends"
|
|
48
50
|
assert ha.path("runtime_server", name="web1", backend="app") == (
|
|
49
|
-
"/
|
|
51
|
+
"/v3/services/haproxy/runtime/servers/web1?backend=app"
|
|
50
52
|
)
|
|
51
53
|
assert get_platform(CADDY).path("config_load") == "/load"
|
|
52
54
|
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Release notes — proxy-aiops 0.3.0
|
|
2
|
-
|
|
3
|
-
Previous release: 0.2.0.
|
|
4
|
-
|
|
5
|
-
## Fixed: route priority lost precision and collapsed distinct values
|
|
6
|
-
|
|
7
|
-
Traefik route priority is an int64. Routing it through the float helper rendered it
|
|
8
|
-
in scientific notation (`9.223372036854776e+18`) *and* lost precision, because a
|
|
9
|
-
float64 cannot represent values near 2**63 exactly.
|
|
10
|
-
|
|
11
|
-
The practical consequence, seen on a live Traefik: two routers with **different**
|
|
12
|
-
priorities (…806 and …805) displayed as the **same** number. Route priority decides
|
|
13
|
-
matching order, so this actively misleads anyone debugging which route wins.
|
|
14
|
-
|
|
15
|
-
Integer quantities — route priority, server weight, request totals, session counts —
|
|
16
|
-
now use an exact `as_int` that returns an existing `int` untouched instead of
|
|
17
|
-
round-tripping it through float64. Genuinely fractional values are unchanged.
|
|
18
|
-
|
|
19
|
-
If you parse these fields, the JSON changes from `9.223372036854776e+18` to
|
|
20
|
-
`9223372036854775806`, and counters from `12.0` to `12`.
|
|
21
|
-
|
|
22
|
-
## Live-verified
|
|
23
|
-
|
|
24
|
-
Against **Traefik 3.2.5** and **Caddy 2**: reads cross-checked against each proxy's
|
|
25
|
-
own API, plus the three analyses. See [docs/VERIFICATION.md](docs/VERIFICATION.md) —
|
|
26
|
-
**HAProxy remains unverified** (its Data Plane API setup was not built for this
|
|
27
|
-
round) and is now the largest gap here.
|
|
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
|
|
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
|