fix(mesh): testUpstream dials via ensureBridge so proxy-mode targets are probeable (#1057)

testUpstream short-circuited on hasActiveTunnel + getBridge (pilot-only)
when probing a cross-node alias. Proxy-mode remotes never have a "pilot
tunnel" in the manager's sense, so any operator hitting "Test alias" on
a proxy-mode target returned tunnel_down even when the cross-node dial
via dialMeshTcpStream worked fine.

Replace with await ptm.ensureBridge(target.nodeId), matching the mode-
agnostic dispatch dialMeshTcpStream already uses. ensureBridge returns
an existing pilot tunnel, an existing proxy bridge, or asks
MeshProxyTunnelDialer to dial on demand. The 'no pilot tunnel' vs
'no bridge' distinction goes away (they meant the same thing).

Adds two test cases: probing a proxy-mode remote via on-demand bridge,
and the tunnel_down path when ensureBridge yields null.
This commit is contained in:
Anso
2026-05-15 09:00:39 -04:00
committed by GitHub
parent 3d5f0ffacd
commit 489aab4516
2 changed files with 83 additions and 4 deletions
@@ -261,6 +261,83 @@ describe('MeshService.testUpstream tunnel-down path', () => {
expect(result.where).toBe('no_route');
expect(result.code).toBe('no_route');
});
it('probes a proxy-mode remote via PilotTunnelManager.ensureBridge (bridge dialed on demand)', async () => {
const svc = MeshService.getInstance();
const db = DatabaseService.getInstance();
const localNodeId = db.getNodes()[0].id;
const remoteNodeId = db.addNode({
name: 'edge', type: 'remote', is_default: false,
compose_dir: '/tmp', api_url: 'https://edge.example',
api_token: 'tok', mode: 'proxy',
});
(svc as unknown as { aliasCache: Map<string, unknown> }).aliasCache = new Map([
['db.api.edge.sencho', {
host: 'db.api.edge.sencho',
nodeId: remoteNodeId,
nodeName: 'edge',
stackName: 'api',
serviceName: 'db',
port: 5432,
}],
]);
db.insertMeshStack(remoteNodeId, 'api', 'tester');
const { PilotTunnelManager } = await import('../services/PilotTunnelManager');
const fakeStream = new EventEmitter() as EventEmitter & { destroy: () => void };
fakeStream.destroy = vi.fn();
const fakeBridge = {
openTcpStream: vi.fn().mockReturnValue(fakeStream),
getActiveStreamCount: () => 0,
close: vi.fn(),
};
vi.spyOn(PilotTunnelManager.getInstance(), 'ensureBridge')
.mockResolvedValue(fakeBridge as unknown as Awaited<ReturnType<typeof PilotTunnelManager.prototype.ensureBridge>>);
const probe = svc.testUpstream('db.api.edge.sencho', localNodeId);
// Emit `open` so the probe resolves cleanly.
setImmediate(() => fakeStream.emit('open'));
const result = await probe;
expect(fakeBridge.openTcpStream).toHaveBeenCalledWith({ stack: 'api', service: 'db', port: 5432 });
expect(result.ok).toBe(true);
db.deleteNode(remoteNodeId);
});
it('returns ok:false where=pilot_tunnel when ensureBridge yields null (no reachable bridge)', async () => {
const svc = MeshService.getInstance();
const db = DatabaseService.getInstance();
const localNodeId = db.getNodes()[0].id;
const remoteNodeId = db.addNode({
name: 'edge2', type: 'remote', is_default: false,
compose_dir: '/tmp', api_url: 'https://edge2.example',
api_token: 'tok', mode: 'proxy',
});
(svc as unknown as { aliasCache: Map<string, unknown> }).aliasCache = new Map([
['db.api.edge2.sencho', {
host: 'db.api.edge2.sencho',
nodeId: remoteNodeId,
nodeName: 'edge2',
stackName: 'api',
serviceName: 'db',
port: 5432,
}],
]);
db.insertMeshStack(remoteNodeId, 'api', 'tester');
const { PilotTunnelManager } = await import('../services/PilotTunnelManager');
vi.spyOn(PilotTunnelManager.getInstance(), 'ensureBridge').mockResolvedValue(null);
const result = await svc.testUpstream('db.api.edge2.sencho', localNodeId);
expect(result.ok).toBe(false);
expect(result.where).toBe('pilot_tunnel');
expect(result.code).toBe('tunnel_down');
db.deleteNode(remoteNodeId);
});
});
describe('getSenchoIpFromSubnet', () => {
+6 -4
View File
@@ -1604,11 +1604,13 @@ export class MeshService extends EventEmitter implements MeshForwarderHost {
}
if (target.nodeId !== sourceNodeId) {
// Use ensureBridge so proxy-mode remotes get a bridge dialed
// on demand. Pre-fix this called hasActiveTunnel + getBridge,
// which only checked the pilot-tunnel slot and returned
// tunnel_down for proxy-mode targets even when the regular
// dialMeshTcpStream path worked.
const ptm = PilotTunnelManager.getInstance();
if (!ptm.hasActiveTunnel(target.nodeId)) {
return { ok: false, where: 'pilot_tunnel', code: 'tunnel_down', message: 'no pilot tunnel' };
}
const bridge = ptm.getBridge(target.nodeId);
const bridge = await ptm.ensureBridge(target.nodeId);
if (!bridge) {
return { ok: false, where: 'pilot_tunnel', code: 'tunnel_down', message: 'no bridge' };
}