diff options
| author | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
|---|---|---|
| committer | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
| commit | 3d51aa455006903345f554a2dd90034993796114 (patch) | |
| tree | 62a7be2faf047f5eb7886feebc3b815556f03d7f /test_balancing.py | |
| download | vpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2 vpnem-3d51aa455006903345f554a2dd90034993796114.zip | |
- Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5)
- Smart load balancing via recommendation API
- Windows/Linux client (Go + Wails + sing-box)
- Server API with RealIP detection and connection tracking
- Auto-deployment via vpnui control plane
- Silent Windows installer with UAC elevation
- Load-based server recommendation (no sticky sessions)
- Best Server one-click connection workflow
Diffstat (limited to 'test_balancing.py')
| -rw-r--r-- | test_balancing.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/test_balancing.py b/test_balancing.py new file mode 100644 index 0000000..0f7933d --- /dev/null +++ b/test_balancing.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Real-world load balancing test for vpnem API.""" +import urllib.request +import json +import sys + +BASE = "https://vpn.em-sysadmin.xyz" + +def req(path, method="GET", data=None, headers=None): + """Make HTTP request.""" + url = BASE + path + h = headers or {} + h["Content-Type"] = "application/json" + body = json.dumps(data).encode() if data else None + rq = urllib.request.Request(url, data=body, headers=h, method=method) + try: + resp = urllib.request.urlopen(rq, timeout=10) + return json.loads(resp.read().decode()) + except urllib.error.HTTPError as e: + print(f" ERROR {e.code}: {e.read().decode()[:200]}") + return None + except Exception as e: + print(f" ERROR: {e}") + return None + +def get_recommend(client_ip): + """Get recommendation for a client IP.""" + return req("/api/v1/recommend", headers={"X-Forwarded-For": client_ip}) + +def connect(client_ip, server_ip, node_id): + """Report connection.""" + return req("/api/v1/connect", method="POST", data={ + "server_ip": server_ip, "node_id": node_id + }, headers={"X-Forwarded-For": client_ip}) + +def disconnect(client_ip): + """Report disconnection.""" + return req("/api/v1/disconnect", method="POST", data={ + "server_ip": "" + }, headers={"X-Forwarded-For": client_ip}) + +def main(): + print("=" * 70) + print("VPNEM Load Balancing — Real Server Test") + print("=" * 70) + print() + + # Step 1: Test recommendations for multiple "studios" + print("[1] Testing recommendations for 5 different studios...") + print() + studios = [ + ("195.10.20.1", "Barnaul Studio 1"), + ("195.10.20.2", "Barnaul Studio 2"), + ("91.50.60.1", "Moscow Studio"), + ("46.30.20.1", "Novosibirsk Studio"), + ("178.120.1.1", "Test Studio"), + ] + + recommendations = {} + for ip, name in studios: + rec = get_recommend(ip) + if rec: + server = rec.get("recommended_server_ip", "none") + reason = rec.get("reason", "unknown") + load = rec.get("load_info", "no info") + recommendations[ip] = server + print(f" {name:25s} → {server:15s} ({reason})") + print(f" Load: {load}") + else: + print(f" {name:25s} → ERROR") + + print() + + # Step 2: Check distribution — do different studios get different servers? + print("[2] Distribution check...") + servers_used = list(recommendations.values()) + unique = set(servers_used) + print(f" Studios: {len(studios)}, Recommended IPs: {len(unique)}") + print(f" Servers used: {', '.join(unique)}") + if len(unique) > 1: + print(" ✅ GOOD — different studios get different servers") + else: + print(" ⚠️ All studios got the same server (possible if load is equal)") + + print() + + # Step 3: Simulate connections + print("[3] Simulating connections...") + nodes = { + "5.180.97.181": "nl-multi-181", + "5.180.97.197": "nl-multi-197", + "5.180.97.198": "nl-multi-198", + "5.180.97.199": "nl-multi-199", + } + for ip, name in studios: + srv = recommendations.get(ip) + nid = nodes.get(srv, "nl-multi-181") + resp = connect(ip, srv, nid) + if resp: + rec_srv = resp.get("recommended_server_ip", "none") + print(f" {name:25s} connected → {srv:15s} (next rec: {rec_srv})") + else: + print(f" {name:25s} connect FAILED") + + print() + + # Step 4: Check recommendations AFTER connections (should shift) + print("[4] Recommendations AFTER connections (should re-balance)...") + new_ip = "100.100.100.1" + rec = get_recommend(new_ip) + if rec: + srv = rec.get("recommended_server_ip", "none") + reason = rec.get("reason", "unknown") + load = rec.get("load_info", "no info") + print(f" New Studio ({new_ip}) → {srv:15s} ({reason})") + print(f" Load: {load}") + # The new studio should NOT get the most loaded server + else: + print(f" ERROR getting recommendation") + + print() + + # Step 5: Disconnect one studio, check if recommendation changes + print("[5] Disconnecting first studio and checking rebalance...") + disc_ip = studios[0][0] + disconnect(disc_ip) + print(f" Disconnected {studios[0][1]} ({disc_ip})") + + rec = get_recommend("200.200.200.1") + if rec: + srv = rec.get("recommended_server_ip", "none") + load = rec.get("load_info", "no info") + print(f" New Studio (200.200.200.1) → {srv:15s}") + print(f" Load: {load}") + else: + print(" ERROR") + + print() + + # Step 6: Clean up + print("[6] Cleaning up — disconnecting all test studios...") + for ip, name in studios: + disconnect(ip) + disconnect(new_ip) + disconnect("200.200.200.1") + print(" Done") + + print() + print("=" * 70) + print("Test complete.") + print("=" * 70) + +if __name__ == "__main__": + main() |
