fix: isolate provider egress tunnel diagnostics
This commit is contained in:
@@ -199,6 +199,91 @@ export async function debugSshPool(_config: UniDeskConfig, providerId: string):
|
||||
};
|
||||
}
|
||||
|
||||
export async function debugEgressProxy(_config: UniDeskConfig, providerId: string): Promise<unknown> {
|
||||
const nodesResponse = await coreInternalFetch("/api/nodes");
|
||||
const body = recordValue(recordValue(nodesResponse).body);
|
||||
const nodes = arrayValue(body.nodes);
|
||||
const node = nodes
|
||||
.map((item) => recordValue(item))
|
||||
.find((item) => item.providerId === providerId) ?? null;
|
||||
if (node === null) {
|
||||
return {
|
||||
ok: false,
|
||||
providerId,
|
||||
degradedReason: "provider-not-found",
|
||||
nodesFetch: nodesResponse,
|
||||
next: { fullHealth: "bun scripts/cli.ts debug health" },
|
||||
};
|
||||
}
|
||||
const labels = recordValue(node.labels);
|
||||
const active = Number(labels.providerGatewayEgressProxyActiveTunnels ?? 0);
|
||||
const pending = Number(labels.providerGatewayEgressProxyPendingTunnels ?? 0);
|
||||
const opened = Number(labels.providerGatewayEgressProxyOpenedTunnels ?? 0);
|
||||
const stale = Number(labels.providerGatewayEgressProxyStaleTunnels ?? 0);
|
||||
const enabled = labels.providerGatewayEgressProxy === true;
|
||||
const connected = labels.providerGatewayEgressProxyConnected === true;
|
||||
const sshReady = Number(labels.providerGatewaySshDataPoolReady ?? 0);
|
||||
const activeTunnelDetails = arrayValue(labels.providerGatewayEgressProxyActiveTunnelDetails);
|
||||
const recentClosedTunnels = arrayValue(labels.providerGatewayEgressProxyRecentClosedTunnels);
|
||||
const ok = enabled && connected && stale === 0;
|
||||
return {
|
||||
ok,
|
||||
providerId,
|
||||
node: {
|
||||
providerId: node.providerId,
|
||||
name: node.name,
|
||||
status: node.status,
|
||||
lastHeartbeat: node.lastHeartbeat ?? null,
|
||||
updatedAt: node.updatedAt ?? null,
|
||||
},
|
||||
egressProxy: {
|
||||
enabled,
|
||||
connected,
|
||||
port: labels.providerGatewayEgressProxyPort ?? null,
|
||||
activeTunnels: Number.isFinite(active) ? active : 0,
|
||||
pendingTunnels: Number.isFinite(pending) ? pending : 0,
|
||||
openedTunnels: Number.isFinite(opened) ? opened : 0,
|
||||
staleTunnels: Number.isFinite(stale) ? stale : 0,
|
||||
oldestTunnelAgeMs: labels.providerGatewayEgressProxyOldestTunnelAgeMs ?? null,
|
||||
policy: {
|
||||
openTimeoutMs: labels.providerGatewayEgressProxyOpenTimeoutMs ?? null,
|
||||
idleTimeoutMs: labels.providerGatewayEgressProxyIdleTimeoutMs ?? null,
|
||||
maxTunnelAgeMs: labels.providerGatewayEgressProxyMaxTunnelAgeMs ?? null,
|
||||
staleTunnelIdleMs: labels.providerGatewayEgressProxyStaleTunnelIdleMs ?? null,
|
||||
maxPendingBytes: labels.providerGatewayEgressProxyMaxPendingBytes ?? null,
|
||||
},
|
||||
activeTunnelDetails,
|
||||
activeTunnelDetailsCount: activeTunnelDetails.length,
|
||||
recentClosedTunnels,
|
||||
recentClosedTunnelCount: recentClosedTunnels.length,
|
||||
},
|
||||
controlPlaneSeparation: {
|
||||
sshDataTransport: stringValue(labels.providerGatewaySshDataTransport),
|
||||
sshDataPoolReady: Number.isFinite(sshReady) ? sshReady : 0,
|
||||
sshDataPoolDesired: labels.providerGatewaySshDataPoolDesired ?? null,
|
||||
sshDataPoolClaimed: labels.providerGatewaySshDataPoolClaimed ?? null,
|
||||
note: "SSH/control readiness is reported separately from egress download tunnel activity.",
|
||||
},
|
||||
classification: !enabled
|
||||
? "egress-proxy-disabled"
|
||||
: !connected
|
||||
? "egress-proxy-core-channel-disconnected"
|
||||
: stale > 0
|
||||
? "egress-tunnel-stale"
|
||||
: active > 0
|
||||
? "egress-tunnel-active"
|
||||
: "egress-proxy-ready",
|
||||
outputPolicy: {
|
||||
redaction: "activeTunnelDetails expose target host/port and path/query-key summary only; URL credentials, headers, tokens and query values are not emitted.",
|
||||
boundedRecentClosedTunnels: true,
|
||||
},
|
||||
next: {
|
||||
smoke: `trans ${providerId} argv true`,
|
||||
fullHealth: "bun scripts/cli.ts debug health",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForTask(taskId: string, timeoutMs: number): Promise<unknown> {
|
||||
const started = Date.now();
|
||||
let latest: unknown = null;
|
||||
|
||||
+4
-2
@@ -95,6 +95,7 @@ export function rootHelp(): unknown {
|
||||
{ command: "job cancel <jobId>", description: "Cancel a queued/running async job through the .state/jobs control entry and keep a terminal canceled record." },
|
||||
{ command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." },
|
||||
{ command: "debug ssh-pool <providerId>", description: "Show bounded host.ssh.tcp-pool labels for one provider, including ready/claimed/desired/lastError." },
|
||||
{ command: "debug egress-proxy <providerId>", description: "Show provider-gateway egress proxy tunnel counts, stale tunnel diagnosis, active target summaries, and recent closed tunnel lifecycle without URL credential leakage." },
|
||||
{ command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." },
|
||||
{ command: "debug task <taskId|latest>", description: "Read a dispatched task record from internal core for CLI debugging." },
|
||||
{ command: "network perf [--service code-queue --path /api/tasks/overview?limit=30 --count N --concurrency N --label before|after]", description: "Benchmark frontend -> backend-core -> provider/adapter user-service networking and report latency/proxy-mode distributions." },
|
||||
@@ -539,15 +540,16 @@ function jobHelp(): unknown {
|
||||
|
||||
function debugHelp(): unknown {
|
||||
return {
|
||||
command: "debug health|ssh-pool|dispatch|task",
|
||||
command: "debug health|ssh-pool|egress-proxy|dispatch|task",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts debug health",
|
||||
"bun scripts/cli.ts debug ssh-pool <providerId>",
|
||||
"bun scripts/cli.ts debug egress-proxy <providerId>",
|
||||
"bun scripts/cli.ts debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]",
|
||||
"bun scripts/cli.ts debug task <taskId|latest>",
|
||||
],
|
||||
description: "Debug the real core/provider/dispatch paths. ssh-pool returns bounded host.ssh.tcp-pool labels for one provider; do not use debug commands as formal TEST.md acceptance steps.",
|
||||
description: "Debug the real core/provider/dispatch paths. ssh-pool and egress-proxy are separate so SSH/control readiness is not conflated with long download tunnel activity; do not use debug commands as formal TEST.md acceptance steps.",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,20 @@ function providerGatewaySignal(debug: unknown, providerId: string): ProviderTria
|
||||
providerGatewayVersion: labels.providerGatewayVersion ?? null,
|
||||
hostSshConfigured: labels.hostSshConfigured ?? null,
|
||||
hostSshKeyPresent: labels.hostSshKeyPresent ?? null,
|
||||
sshDataPool: {
|
||||
transport: labels.providerGatewaySshDataTransport ?? null,
|
||||
desired: labels.providerGatewaySshDataPoolDesired ?? null,
|
||||
ready: labels.providerGatewaySshDataPoolReady ?? null,
|
||||
claimed: labels.providerGatewaySshDataPoolClaimed ?? null,
|
||||
},
|
||||
egressProxy: {
|
||||
enabled: labels.providerGatewayEgressProxy ?? null,
|
||||
connected: labels.providerGatewayEgressProxyConnected ?? null,
|
||||
activeTunnels: labels.providerGatewayEgressProxyActiveTunnels ?? null,
|
||||
pendingTunnels: labels.providerGatewayEgressProxyPendingTunnels ?? null,
|
||||
staleTunnels: labels.providerGatewayEgressProxyStaleTunnels ?? null,
|
||||
oldestTunnelAgeMs: labels.providerGatewayEgressProxyOldestTunnelAgeMs ?? null,
|
||||
},
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user