diff --git a/services/mam-api/src/routes/cluster.js b/services/mam-api/src/routes/cluster.js index 0b686e3..4d81aa5 100644 --- a/services/mam-api/src/routes/cluster.js +++ b/services/mam-api/src/routes/cluster.js @@ -66,6 +66,32 @@ router.post('/heartbeat', async (req, res, next) => { } catch (err) { next(err); } }); +// GET /:id/ping – probe the node's api_url/health endpoint directly +router.get('/:id/ping', async (req, res, next) => { + try { + const r = await pool.query( + 'SELECT id, hostname, api_url FROM cluster_nodes WHERE id = $1', + [req.params.id] + ); + if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' }); + + const node = r.rows[0]; + if (!node.api_url) return res.json({ reachable: false, reason: 'no api_url registered' }); + + const start = Date.now(); + try { + const upstream = await fetch(`${node.api_url}/health`, { + signal: AbortSignal.timeout(4000), + }); + const latency_ms = Date.now() - start; + const body = await upstream.json().catch(() => ({})); + res.json({ reachable: upstream.ok, latency_ms, status: upstream.status, agent: body }); + } catch (err) { + res.json({ reachable: false, latency_ms: Date.now() - start, reason: err.message }); + } + } catch (err) { next(err); } +}); + // DELETE /:id – deregister a node router.delete('/:id', async (req, res, next) => { try { @@ -73,7 +99,7 @@ router.delete('/:id', async (req, res, next) => { 'DELETE FROM cluster_nodes WHERE id = $1 RETURNING id', [req.params.id] ); - if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' }); + if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' });; res.json({ ok: true }); } catch (err) { next(err); } });