test: add end-to-end and load verification
This commit is contained in:
242
scripts/functional_traffic.py
Normal file
242
scripts/functional_traffic.py
Normal file
@@ -0,0 +1,242 @@
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from copy import deepcopy
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
BASE_URL = os.getenv(
|
||||
"BASE_URL",
|
||||
"http://localhost:8000",
|
||||
)
|
||||
|
||||
ENDPOINT = f"{BASE_URL}/api/v1/cdr/raw"
|
||||
|
||||
RUN_ID = os.getenv(
|
||||
"RUN_ID",
|
||||
uuid.uuid4().hex[:8],
|
||||
)
|
||||
|
||||
|
||||
def valid_payload(index: int) -> dict:
|
||||
start = int(time.time())
|
||||
|
||||
return {
|
||||
"raw_id": f"functional-{RUN_ID}-{index:04d}",
|
||||
"src_ip": "10.240.12.1",
|
||||
"device_id": "310410998877665",
|
||||
"caller_num": "+14155552671",
|
||||
"svc": "4G_DATA",
|
||||
"up_bytes": 1024,
|
||||
"down_bytes": 4096,
|
||||
"rat": 6,
|
||||
"lac": "4021",
|
||||
"ci": "8812",
|
||||
"epoch_start": start,
|
||||
"epoch_end": start + 300,
|
||||
}
|
||||
|
||||
|
||||
def send_json(payload: dict) -> int | str:
|
||||
body = json.dumps(
|
||||
payload
|
||||
).encode("utf-8")
|
||||
|
||||
return send_raw(body)
|
||||
|
||||
|
||||
def send_raw(body: bytes) -> int | str:
|
||||
request = Request(
|
||||
ENDPOINT,
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
with urlopen(
|
||||
request,
|
||||
timeout=10,
|
||||
) as response:
|
||||
return response.status
|
||||
|
||||
except HTTPError as exc:
|
||||
return exc.code
|
||||
|
||||
except URLError as exc:
|
||||
return f"network_error:{exc.reason}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
requests: list[
|
||||
tuple[str, dict | bytes]
|
||||
] = []
|
||||
|
||||
valid_records = [
|
||||
valid_payload(index)
|
||||
for index in range(1000)
|
||||
]
|
||||
|
||||
# 1000 valid CDRs
|
||||
for payload in valid_records:
|
||||
requests.append(
|
||||
("valid", payload)
|
||||
)
|
||||
|
||||
# 20 missing caller_num
|
||||
for index in range(20):
|
||||
payload = valid_payload(
|
||||
2000 + index
|
||||
)
|
||||
|
||||
payload.pop(
|
||||
"caller_num"
|
||||
)
|
||||
|
||||
requests.append(
|
||||
("missing_caller_num", payload)
|
||||
)
|
||||
|
||||
# 10 malformed JSON requests
|
||||
for _ in range(10):
|
||||
requests.append(
|
||||
(
|
||||
"invalid_json",
|
||||
b'{"raw_id": invalid}',
|
||||
)
|
||||
)
|
||||
|
||||
# 10 invalid RAT values
|
||||
for index in range(10):
|
||||
payload = valid_payload(
|
||||
3000 + index
|
||||
)
|
||||
|
||||
payload["rat"] = 99
|
||||
|
||||
requests.append(
|
||||
("invalid_rat", payload)
|
||||
)
|
||||
|
||||
# 10 invalid epochs
|
||||
for index in range(10):
|
||||
payload = valid_payload(
|
||||
4000 + index
|
||||
)
|
||||
|
||||
payload["epoch_start"] = (
|
||||
payload["epoch_end"] + 100
|
||||
)
|
||||
|
||||
requests.append(
|
||||
("invalid_epoch", payload)
|
||||
)
|
||||
|
||||
# 20 duplicates of already accepted valid records.
|
||||
for payload in valid_records[:20]:
|
||||
requests.append(
|
||||
(
|
||||
"duplicate",
|
||||
deepcopy(payload),
|
||||
)
|
||||
)
|
||||
|
||||
random.shuffle(
|
||||
requests
|
||||
)
|
||||
|
||||
results = Counter()
|
||||
|
||||
started_at = time.perf_counter()
|
||||
|
||||
with ThreadPoolExecutor(
|
||||
max_workers=50
|
||||
) as executor:
|
||||
futures = []
|
||||
|
||||
for request_type, payload in requests:
|
||||
if isinstance(
|
||||
payload,
|
||||
bytes,
|
||||
):
|
||||
future = executor.submit(
|
||||
send_raw,
|
||||
payload,
|
||||
)
|
||||
else:
|
||||
future = executor.submit(
|
||||
send_json,
|
||||
payload,
|
||||
)
|
||||
|
||||
futures.append(
|
||||
(
|
||||
request_type,
|
||||
future,
|
||||
)
|
||||
)
|
||||
|
||||
for request_type, future in futures:
|
||||
status = future.result()
|
||||
|
||||
results[
|
||||
(
|
||||
request_type,
|
||||
status,
|
||||
)
|
||||
] += 1
|
||||
|
||||
elapsed = (
|
||||
time.perf_counter()
|
||||
- started_at
|
||||
)
|
||||
|
||||
print()
|
||||
print(
|
||||
f"Run ID: {RUN_ID}"
|
||||
)
|
||||
|
||||
print(
|
||||
f"Total requests: {len(requests)}"
|
||||
)
|
||||
|
||||
print(
|
||||
f"Elapsed: {elapsed:.2f}s"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
for (
|
||||
request_type,
|
||||
status,
|
||||
), count in sorted(
|
||||
results.items(),
|
||||
key=lambda item: str(
|
||||
item[0]
|
||||
),
|
||||
):
|
||||
print(
|
||||
f"{request_type:24} "
|
||||
f"status={status!s:8} "
|
||||
f"count={count}"
|
||||
)
|
||||
|
||||
print()
|
||||
print("Expected:")
|
||||
print(" valid → 1000 x 202")
|
||||
print(" duplicate → 20 x 202")
|
||||
print(" missing_caller → 20 x 400")
|
||||
print(" invalid_json → 10 x 400")
|
||||
print(" invalid_rat → 10 x 400")
|
||||
print(" invalid_epoch → 10 x 400")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user