Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Diagnostic could be improved #498

Merged
merged 3 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/aleph/vm/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ class Settings(BaseSettings):
WATCH_FOR_UPDATES = True

API_SERVER = "https://official.aleph.cloud"
# Connect to the Quad9 VPN provider using their IPv4 and IPv6 addresses.
CONNECTIVITY_IPV4_URL = "https://9.9.9.9/"
CONNECTIVITY_IPV6_URL = "https://[2620:fe::fe]/"
CONNECTIVITY_DNS_HOSTNAME = "example.org"

USE_JAILER = True
# System logs make boot ~2x slower
PRINT_SYSTEM_LOGS = False
Expand Down
2 changes: 2 additions & 0 deletions src/aleph/vm/orchestrator/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
run_code_from_hostname,
run_code_from_path,
status_check_fastapi,
status_check_host,
status_check_version,
status_public_config,
update_allocations,
Expand Down Expand Up @@ -90,6 +91,7 @@ async def allow_cors_on_endpoint(request: web.Request):
allow_cors_on_endpoint,
),
web.get("/status/check/fastapi", status_check_fastapi),
web.get("/status/check/host", status_check_host),
web.get("/status/check/version", status_check_version),
web.get("/status/config", status_public_config),
web.static("/static", Path(__file__).parent / "views/static"),
Expand Down
27 changes: 27 additions & 0 deletions src/aleph/vm/orchestrator/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
from aleph.vm.orchestrator.pubsub import PubSub
from aleph.vm.orchestrator.resources import Allocation
from aleph.vm.orchestrator.run import run_code_on_request, start_persistent_vm
from aleph.vm.orchestrator.views.host_status import (
check_dns_ipv4,
check_dns_ipv6,
check_domain_resolution_ipv4,
check_domain_resolution_ipv6,
check_host_egress_ipv4,
check_host_egress_ipv6,
)
from aleph.vm.pool import VmPool
from aleph.vm.utils import (
HostNotFoundError,
Expand Down Expand Up @@ -169,6 +177,25 @@ async def status_check_fastapi(request: web.Request):
return web.json_response(result, status=200 if all(result.values()) else 503)


async def status_check_host(request: web.Request):
"""Check that the platform is supported and configured correctly"""

result = {
"ipv4": {
"egress": await check_host_egress_ipv4(),
"dns": await check_dns_ipv4(),
"domain": await check_domain_resolution_ipv4(),
},
"ipv6": {
"egress": await check_host_egress_ipv6(),
"dns": await check_dns_ipv6(),
"domain": await check_domain_resolution_ipv6(),
},
}
result_status = 200 if all(result["ipv4"].values()) and all(result["ipv6"].values()) else 503
return web.json_response(result, status=result_status)


async def status_check_version(request: web.Request):
"""Check if the software is running a version equal or newer than the given one"""
reference_str: Optional[str] = request.query.get("reference")
Expand Down
85 changes: 85 additions & 0 deletions src/aleph/vm/orchestrator/views/host_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import logging
import socket
from typing import Any, Awaitable, Callable, Tuple

import aiohttp

from aleph.vm.conf import settings

logger = logging.getLogger(__name__)


def return_false_on_timeout(func: Callable[..., Awaitable[Any]]) -> Callable[..., Awaitable[bool]]:
async def wrapper(*args: Any, **kwargs: Any) -> bool:
try:
return await func(*args, **kwargs)
except TimeoutError:
logger.warning(f"Timeout while checking {func.__name__}")
return False

return wrapper


async def check_ip_connectivity(url: str, socket_family: socket.AddressFamily = socket.AF_UNSPEC) -> bool:
timeout = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(), timeout=timeout) as session:
async with session.get(url, socket_family=socket_family) as resp:
# We expect the Quad9 endpoints to return a 404 error, but other endpoints may return a 200
if resp.status not in (200, 404):
resp.raise_for_status()
return True


@return_false_on_timeout
async def check_host_egress_ipv4() -> bool:
"""Check if the host has IPv4 connectivity."""
return await check_ip_connectivity(settings.CONNECTIVITY_IPV4_URL)


@return_false_on_timeout
async def check_host_egress_ipv6() -> bool:
"""Check if the host has IPv6 connectivity."""
return await check_ip_connectivity(settings.CONNECTIVITY_IPV6_URL)


async def resolve_dns(hostname: str) -> Tuple[str, str]:
info_inet, info_inet6 = socket.getaddrinfo(hostname, 80, proto=socket.IPPROTO_TCP)
ipv4 = info_inet[4][0]
ipv6 = info_inet6[4][0]
return ipv4, ipv6


async def check_dns_ipv4() -> bool:
"""Check if DNS resolution is working via IPv4."""
ipv4, _ = await resolve_dns(settings.CONNECTIVITY_DNS_HOSTNAME)
return bool(ipv4)


async def check_dns_ipv6() -> bool:
"""Check if DNS resolution is working via IPv6."""
_, ipv6 = await resolve_dns(settings.CONNECTIVITY_DNS_HOSTNAME)
return bool(ipv6)


async def check_domain_resolution_ipv4() -> bool:
"""Check if the host's hostname resolves to an IPv4 address."""
ipv4, _ = await resolve_dns(settings.DOMAIN_NAME)
return bool(ipv4)


async def check_domain_resolution_ipv6() -> bool:
"""Check if the host's hostname resolves to an IPv6 address."""
_, ipv6 = await resolve_dns(settings.DOMAIN_NAME)
return bool(ipv6)


@return_false_on_timeout
async def check_domain_ipv4() -> bool:
"""Check if the host's hostname is accessible via IPv4."""
return await check_ip_connectivity(settings.DOMAIN_NAME, socket.AF_INET)


@return_false_on_timeout
async def check_domain_ipv6() -> bool:
"""Check if the host's hostname is accessible via IPv6."""
return await check_ip_connectivity(settings.DOMAIN_NAME, socket.AF_INET6)
34 changes: 33 additions & 1 deletion src/aleph/vm/orchestrator/views/static/helpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
async function fetchApiStatus () {
async function fetchFastapiCheckStatus () {
const q = await fetch('/status/check/fastapi');
let res = {
status: q.status,
Expand All @@ -12,8 +12,10 @@ async function fetchApiStatus () {
case 503:
res.status = " is not working properly ❌";
res.details = await q.json();
break;
case 500:
res.status = " ❌ Failed";
break;
default:
res.status = q.status;
}
Expand All @@ -22,6 +24,36 @@ async function fetchApiStatus () {
return res;
}

async function fetchHostCheckStatus () {
const q = await fetch('/status/check/host');
let res = {
status: q.status,
details: []
}
if(q.ok){
res.status = " is working properly ✅";
}
else {
switch(Number(q.status)){
case 503:
res.status = " is not working properly ❌";
res.details = await q.json();
break;
case 500:
res.status = " ❌ Failed";
break;
default:
res.status = q.status;
}
}

return res;
}

function objectToString (obj) {
return Object.entries(obj).reduce((acc, [k, v]) => acc + `<li>${k}: <span style="color: ${v ? 'green' : 'red'}">${v}</span></li>\n`, '');
}

const buildQueryParams = (params) => Object.entries(params).reduce((acc, [k, v]) => acc + `${k}=${v}&`, '?').slice(0, -1);

const isLatestRelease = async () => {
Expand Down
17 changes: 10 additions & 7 deletions src/aleph/vm/orchestrator/views/static/main.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
body {
font-family: IBM Plex Regular, monospace;
white-space: normal;
margin: auto;
max-width: 800px;
}

main {
width: 90vw;
margin: 2vh auto;
max-width: 800px;
}

progress {
Expand Down Expand Up @@ -36,29 +39,29 @@ progress {

@keyframes move {
0% {
height: 20px;
height: 10px;
}

50% {
height: 10px;
height: 5px;
}

100% {
height: 20px;
height: 10px;
}
}

@keyframes move2 {
0% {
height: 10px;
height: 5px;
}

50% {
height: 20px;
height: 10px;
}

100% {
height: 10px;
height: 5px;
}
}

Expand Down Expand Up @@ -97,4 +100,4 @@ progress {
footer{
font-size: 70%;
opacity: .75;
}
}
Loading
Loading