Add SystemTest Agent and Controller
This commit is contained in:
commit
53f523f187
31 files changed
+9150
No files matched your search
@@ -0,0 +1,136 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import ipaddress
|
||||
import json
|
||||
import socket
|
||||
from urllib.error import URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
DISCOVERY_PATH = "/api/v1/discovery"
|
||||
AUTO_VALUES = {"", "auto", "discover", "lan"}
|
||||
|
||||
|
||||
def _is_private_ipv4(value: str) -> bool:
|
||||
try:
|
||||
ip = ipaddress.ip_address(value)
|
||||
except ValueError:
|
||||
return False
|
||||
return ip.version == 4 and ip.is_private and not ip.is_loopback
|
||||
|
||||
|
||||
def _local_private_ipv4s() -> list[str]:
|
||||
addresses: set[str] = set()
|
||||
try:
|
||||
for value in socket.gethostbyname_ex(socket.gethostname())[2]:
|
||||
if _is_private_ipv4(value):
|
||||
addresses.add(value)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# UDP connect does not send packets; it reveals the chosen outbound address.
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
||||
sock.connect(("8.8.8.8", 80))
|
||||
value = sock.getsockname()[0]
|
||||
if _is_private_ipv4(value):
|
||||
addresses.add(value)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return sorted(addresses)
|
||||
|
||||
|
||||
def _candidate_hosts() -> list[str]:
|
||||
hosts: set[str] = {"127.0.0.1"}
|
||||
for address in _local_private_ipv4s():
|
||||
hosts.add(address)
|
||||
network = ipaddress.ip_network(f"{address}/24", strict=False)
|
||||
for host in network.hosts():
|
||||
hosts.add(str(host))
|
||||
return sorted(hosts, key=lambda value: tuple(int(part) for part in value.split(".")))
|
||||
|
||||
|
||||
def _probe(base_url: str, timeout_seconds: float) -> str | None:
|
||||
url = f"{base_url.rstrip('/')}{DISCOVERY_PATH}"
|
||||
request = Request(url, headers={"Accept": "application/json"})
|
||||
try:
|
||||
with urlopen(request, timeout=timeout_seconds) as response:
|
||||
body = response.read()
|
||||
except (OSError, URLError):
|
||||
return None
|
||||
|
||||
try:
|
||||
payload = json.loads(body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
if payload.get("service") != "Agent-S-Controller":
|
||||
return None
|
||||
discovered = str(payload.get("base_url") or base_url).rstrip("/")
|
||||
return discovered or None
|
||||
|
||||
|
||||
def discover_controller_base_url(
|
||||
ports: list[int],
|
||||
timeout_seconds: float,
|
||||
workers: int = 96,
|
||||
log=print,
|
||||
) -> str | None:
|
||||
hosts = _candidate_hosts()
|
||||
attempts = [(host, port) for port in ports for host in hosts]
|
||||
if not attempts:
|
||||
return None
|
||||
|
||||
log(
|
||||
"[agent] discovering controller on "
|
||||
+ f"{len(hosts)} hosts x ports {', '.join(str(p) for p in ports)} ..."
|
||||
)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=min(workers, len(attempts))) as executor:
|
||||
future_map = {
|
||||
executor.submit(_probe, f"http://{host}:{port}", timeout_seconds): (host, port)
|
||||
for host, port in attempts
|
||||
}
|
||||
for future in concurrent.futures.as_completed(future_map):
|
||||
found = future.result()
|
||||
if found:
|
||||
host, port = future_map[future]
|
||||
log(f"[agent] discovered controller at {found} ({host}:{port})")
|
||||
for pending in future_map:
|
||||
pending.cancel()
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def resolve_controller_base_url(
|
||||
configured_base_url: str,
|
||||
discovery_enabled: bool,
|
||||
discovery_ports: list[int],
|
||||
timeout_seconds: float,
|
||||
log=print,
|
||||
) -> str:
|
||||
configured = (configured_base_url or "auto").strip().rstrip("/")
|
||||
if configured.lower() not in AUTO_VALUES:
|
||||
found = _probe(configured, timeout_seconds)
|
||||
if found:
|
||||
return found
|
||||
if not discovery_enabled:
|
||||
return configured
|
||||
log(f"[agent] configured controller is not reachable yet: {configured}")
|
||||
|
||||
if discovery_enabled:
|
||||
found = discover_controller_base_url(
|
||||
ports=discovery_ports,
|
||||
timeout_seconds=timeout_seconds,
|
||||
log=log,
|
||||
)
|
||||
if found:
|
||||
return found
|
||||
|
||||
ports = ", ".join(str(port) for port in discovery_ports)
|
||||
raise RuntimeError(
|
||||
"Controller was not found. Start the controller first, allow TCP "
|
||||
f"{ports} through the firewall, or start the agent with "
|
||||
"start_agent.bat http://<controller-lan-ip>:8001"
|
||||
)
|
||||
Reference in new issue
Block a user