Files
SQS-service/tests/platform_probe.py
T

255 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""tests/platform_probe.py — диагностика транспортного слоя платформы (HTTP контейнер).
Проверяет, как платформа (шлюз/ingress/DDoS-Guard) пропускает трафик к сервису,
НЕЗАВИСИМО от бизнес-логики SQS. Запускать и на ВМ, и на локальной машине
для сравнения путей. Только stdlib + boto3.
Блоки:
A — через /health: burst 500 параллельных, большие заголовки, медленное тело,
keep-alive x50, HTTP/1.0, HEAD, POST.
B — через SQS API (транспорт): SendMessage 32KB, Receive 10x32KB,
long-poll 20s (фактическая длительность), chunked-тело.
"""
import http.client
import json
import os
import socket
import ssl
import sys
import threading
import time
import uuid
import boto3
from botocore.config import Config
ENDPOINT = os.environ.get("ENDPOINT_URL", "https://sqs.containerk8s.dev.nubes.ru")
HOST = ENDPOINT.replace("https://", "").replace("http://", "").rstrip("/")
PORT = 443
REGION = os.environ.get("REGION", "us-east-1")
MODE = os.environ.get("PROBE_MODE", "unknown")
results = {}
def _conn():
c = http.client.HTTPSConnection(HOST, PORT, timeout=30)
return c
def record(name, value):
results[name] = value
print("%s: %s" % (name, json.dumps(value, ensure_ascii=False)), flush=True)
def http_get(path, headers=None, method="GET", body=None):
c = _conn()
try:
c.request(method, path, body=body, headers=headers or {})
r = c.getresponse()
data = r.read()
return r.status, dict(r.getheaders()), data
finally:
c.close()
# --- A1: burst 500 параллельных /health ---
def a1_burst():
n = 500
ok = err = 0
lat = []
lock = threading.Lock()
def one():
nonlocal ok, err
t0 = time.time()
try:
st, _, _ = http_get("/health")
with lock:
lat.append(time.time() - t0)
if st == 200:
ok += 1
else:
err += 1
except Exception:
with lock:
err += 1
threads = [threading.Thread(target=one) for _ in range(n)]
t0 = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
total = time.time() - t0
lat.sort()
record("A1_burst_500", {
"ok": ok, "err": err, "total_s": round(total, 2),
"p50": round(lat[len(lat) // 2], 3) if lat else None,
"p95": round(lat[int(len(lat) * 0.95)], 3) if lat else None,
"max": round(lat[-1], 3) if lat else None,
})
# --- A2: большие заголовки ---
def a2_big_headers():
for kb in (8, 16, 64):
h = {"User-Agent": "platform-probe", "X-Pad": "x" * (kb * 1024)}
try:
st, _, _ = http_get("/health", headers=h)
record("A2_header_%dkb" % kb, st)
except Exception as e:
record("A2_header_%dkb" % kb, type(e).__name__ + ": " + str(e)[:80])
# --- A3: медленное тело (1 байт / 0.5с, Content-Length 10) ---
def a3_slow_body():
try:
ctx = ssl.create_default_context()
raw = ctx.wrap_socket(socket.create_connection((HOST, PORT), timeout=15),
server_hostname=HOST)
raw.sendall(b"POST /health HTTP/1.1\r\nHost: %s\r\nContent-Length: 10\r\nConnection: close\r\n\r\n" % HOST.encode())
t0 = time.time()
for _ in range(10):
raw.sendall(b"x")
time.sleep(0.5)
resp = raw.recv(65536)
dt = time.time() - t0
raw.close()
first_line = resp.split(b"\r\n", 1)[0].decode("latin1", "replace") if resp else "(no response)"
record("A3_slow_body", {"resp": first_line, "t_s": round(dt, 2)})
except Exception as e:
record("A3_slow_body", type(e).__name__ + ": " + str(e)[:80])
# --- A4: keep-alive 50 запросов на одном соединении ---
def a4_keepalive():
ok = err = 0
c = _conn()
try:
for _ in range(50):
try:
c.request("GET", "/health")
r = c.getresponse()
r.read()
if r.status == 200:
ok += 1
else:
err += 1
except Exception:
err += 1
break
finally:
c.close()
record("A4_keepalive_50", {"ok": ok, "err": err})
# --- A5: HTTP/1.0, HEAD, POST ---
def a5_variants():
try:
ctx = ssl.create_default_context()
raw = ctx.wrap_socket(socket.create_connection((HOST, PORT), timeout=15),
server_hostname=HOST)
raw.sendall(b"GET /health HTTP/1.0\r\nHost: %s\r\n\r\n" % HOST.encode())
resp = raw.recv(4096)
raw.close()
record("A5_http1.0", resp.split(b"\r\n", 1)[0].decode("latin1", "replace") if resp else "(no response)")
except Exception as e:
record("A5_http1.0", type(e).__name__ + ": " + str(e)[:80])
try:
st, _, _ = http_get("/health", method="HEAD")
record("A5_head", st)
except Exception as e:
record("A5_head", type(e).__name__ + ": " + str(e)[:80])
try:
st, _, _ = http_get("/health", method="POST", body=b"x")
record("A5_post", st)
except Exception as e:
record("A5_post", type(e).__name__ + ": " + str(e)[:80])
# --- B6: SendMessage 32KB (большие тела платформа вешает на ~51с — MTU, см. HISTORY) ---
def b6_big_send(sqs, url):
t0 = time.time()
try:
sqs.send_message(QueueUrl=url, MessageBody="x" * (32 * 1024))
record("B6_send_32kb", {"ok": True, "t_s": round(time.time() - t0, 3)})
except Exception as e:
record("B6_send_32kb", {"ok": False, "err": type(e).__name__ + ": " + str(e)[:80], "t_s": round(time.time() - t0, 3)})
# --- B8: Receive 10 x 32KB ---
def b8_big_receive(sqs, url):
for _ in range(10):
sqs.send_message(QueueUrl=url, MessageBody="y" * (32 * 1024))
t0 = time.time()
try:
r = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=10, WaitTimeSeconds=1)
msgs = r.get("Messages", [])
total = sum(len(m["Body"]) for m in msgs)
record("B8_receive_10x32kb", {"ok": True, "msgs": len(msgs), "bytes": total, "t_s": round(time.time() - t0, 3)})
for m in msgs:
try:
sqs.delete_message(QueueUrl=url, ReceiptHandle=m["ReceiptHandle"])
except Exception:
pass
except Exception as e:
record("B8_receive_10x32kb", {"ok": False, "err": type(e).__name__ + ": " + str(e)[:80], "t_s": round(time.time() - t0, 3)})
# --- B9: long-poll 20s — фактическая длительность ---
def b9_longpoll(sqs, url):
t0 = time.time()
try:
sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=1, WaitTimeSeconds=20)
record("B9_longpoll_20s", {"ok": True, "t_s": round(time.time() - t0, 2)})
except Exception as e:
record("B9_longpoll_20s", {"ok": False, "err": type(e).__name__ + ": " + str(e)[:80], "t_s": round(time.time() - t0, 2)})
# --- B7: chunked-тело ---
def b7_chunked():
try:
ctx = ssl.create_default_context()
raw = ctx.wrap_socket(socket.create_connection((HOST, PORT), timeout=15),
server_hostname=HOST)
raw.sendall(b"POST /health HTTP/1.1\r\nHost: %s\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n" % HOST.encode())
raw.sendall(b"5\r\nhello\r\n")
raw.sendall(b"5\r\nworld\r\n")
raw.sendall(b"0\r\n\r\n")
resp = raw.recv(4096)
raw.close()
record("B7_chunked", resp.split(b"\r\n", 1)[0].decode("latin1", "replace") if resp else "(no response)")
except Exception as e:
record("B7_chunked", type(e).__name__ + ": " + str(e)[:80])
def main():
sqs = boto3.client("sqs", endpoint_url=ENDPOINT, region_name=REGION,
config=Config(connect_timeout=10, read_timeout=30, retries={"max_attempts": 1}))
q = "platprobe-%s" % int(time.time())
url = sqs.create_queue(QueueName=q)["QueueUrl"]
print("queue: %s" % url, flush=True)
try:
a1_burst()
a2_big_headers()
a3_slow_body()
a4_keepalive()
a5_variants()
b6_big_send(sqs, url)
b7_chunked()
b8_big_receive(sqs, url)
b9_longpoll(sqs, url)
finally:
try:
sqs.delete_queue(QueueUrl=url)
except Exception:
pass
print("MODE=%s ENDPOINT=%s" % (MODE, ENDPOINT), flush=True)
print("SUMMARY: %s" % json.dumps(results, ensure_ascii=False), flush=True)
if __name__ == "__main__":
sys.exit(main())