Merge pull request #893 from pikasTech/fix/v02-metrics-sidecar-http-probe
fix: use HTTP request for v02 metrics health probes
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createServer } from "node:http";
|
||||
import { createServer, request as httpRequest } from "node:http";
|
||||
import { request as httpsRequest } from "node:https";
|
||||
import { realpathSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -23,26 +24,49 @@ function metricLabels(env) {
|
||||
return `service="${escapeLabelValue(service)}",namespace="${escapeLabelValue(namespace)}",gitops_target="${escapeLabelValue(gitopsTarget)}"`;
|
||||
}
|
||||
|
||||
export async function probeTarget({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS, fetchImpl = globalThis.fetch } = {}) {
|
||||
export function requestTarget({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS } = {}) {
|
||||
const configured = typeof targetUrl === "string" && targetUrl.trim().length > 0;
|
||||
if (!configured || typeof fetchImpl !== "function") {
|
||||
if (!configured) return Promise.resolve({ configured: false, success: false, statusCode: 0, durationSeconds: 0 });
|
||||
|
||||
let url;
|
||||
try {
|
||||
url = new URL(targetUrl);
|
||||
} catch {
|
||||
return Promise.resolve({ configured: true, success: false, statusCode: 0, durationSeconds: 0 });
|
||||
}
|
||||
|
||||
const requestImpl = url.protocol === "https:" ? httpsRequest : url.protocol === "http:" ? httpRequest : null;
|
||||
if (!requestImpl) return Promise.resolve({ configured: true, success: false, statusCode: 0, durationSeconds: 0 });
|
||||
|
||||
const started = Date.now();
|
||||
return new Promise((resolve) => {
|
||||
const finish = (result) => resolve({
|
||||
configured: true,
|
||||
success: false,
|
||||
statusCode: 0,
|
||||
durationSeconds: (Date.now() - started) / 1000,
|
||||
...result
|
||||
});
|
||||
const request = requestImpl(url, { method: "GET", timeout: timeoutMs }, (response) => {
|
||||
response.resume();
|
||||
response.on("end", () => finish({ success: Boolean(response.statusCode && response.statusCode >= 200 && response.statusCode < 300), statusCode: response.statusCode || 0 }));
|
||||
});
|
||||
request.on("timeout", () => request.destroy());
|
||||
request.on("error", () => finish({}));
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
export async function probeTarget({ targetUrl, timeoutMs = DEFAULT_TARGET_TIMEOUT_MS, probeImpl = requestTarget } = {}) {
|
||||
const configured = typeof targetUrl === "string" && targetUrl.trim().length > 0;
|
||||
if (!configured || typeof probeImpl !== "function") {
|
||||
return { configured: false, success: false, statusCode: 0, durationSeconds: 0 };
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const started = Date.now();
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const response = await fetchImpl(targetUrl, { signal: controller.signal });
|
||||
return {
|
||||
configured: true,
|
||||
success: response.ok,
|
||||
statusCode: response.status || 0,
|
||||
durationSeconds: (Date.now() - started) / 1000
|
||||
};
|
||||
return await probeImpl({ targetUrl, timeoutMs });
|
||||
} catch {
|
||||
const started = Date.now();
|
||||
return { configured: true, success: false, statusCode: 0, durationSeconds: (Date.now() - started) / 1000 };
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +109,7 @@ function sendJson(response, statusCode, body) {
|
||||
response.end(payload);
|
||||
}
|
||||
|
||||
export function createMetricsSidecarServer({ env = process.env, fetchImpl = globalThis.fetch } = {}) {
|
||||
export function createMetricsSidecarServer({ env = process.env, probeImpl = requestTarget } = {}) {
|
||||
return createServer(async (request, response) => {
|
||||
const url = new URL(request.url || "/", "http://hwlab-metrics.local");
|
||||
if (request.method === "GET" && (url.pathname === "/health" || url.pathname === "/health/live")) {
|
||||
@@ -98,7 +122,7 @@ export function createMetricsSidecarServer({ env = process.env, fetchImpl = glob
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/metrics") {
|
||||
const timeoutMs = Number.parseInt(env.HWLAB_METRICS_TARGET_TIMEOUT_MS || "", 10) || DEFAULT_TARGET_TIMEOUT_MS;
|
||||
const probe = await probeTarget({ targetUrl: env.HWLAB_METRICS_TARGET_URL, timeoutMs, fetchImpl });
|
||||
const probe = await probeTarget({ targetUrl: env.HWLAB_METRICS_TARGET_URL, timeoutMs, probeImpl });
|
||||
const payload = buildMetricsText({ env, probe });
|
||||
response.writeHead(200, {
|
||||
"content-type": "text/plain; version=0.0.4; charset=utf-8",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createServer } from "node:http";
|
||||
import { once } from "node:events";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
import { buildMetricsText, isMainModule, sanitizeLabelValue } from "./metrics-sidecar.mjs";
|
||||
import { buildMetricsText, isMainModule, probeTarget, requestTarget, sanitizeLabelValue } from "./metrics-sidecar.mjs";
|
||||
|
||||
test("metrics text exposes stable HWLAB labels without sensitive identifiers", () => {
|
||||
const text = buildMetricsText({
|
||||
@@ -26,6 +28,40 @@ test("label sanitization keeps metrics labels low-risk", () => {
|
||||
assert.equal(sanitizeLabelValue(""), "unknown");
|
||||
});
|
||||
|
||||
test("health target probe uses Node HTTP request against cluster-local style targets", async () => {
|
||||
const server = createServer((request, response) => {
|
||||
if (request.url === "/health/live") {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ status: "ok" }));
|
||||
return;
|
||||
}
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
});
|
||||
server.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
try {
|
||||
const address = server.address();
|
||||
const probe = await requestTarget({ targetUrl: `http://127.0.0.1:${address.port}/health/live`, timeoutMs: 1000 });
|
||||
assert.equal(probe.configured, true);
|
||||
assert.equal(probe.success, true);
|
||||
assert.equal(probe.statusCode, 200);
|
||||
assert.ok(probe.durationSeconds >= 0);
|
||||
} finally {
|
||||
server.close();
|
||||
await once(server, "close");
|
||||
}
|
||||
});
|
||||
|
||||
test("health target probe degrades invalid URLs to failed metrics", async () => {
|
||||
const probe = await probeTarget({ targetUrl: "not a url", timeoutMs: 1000 });
|
||||
assert.deepEqual({ configured: probe.configured, success: probe.success, statusCode: probe.statusCode }, {
|
||||
configured: true,
|
||||
success: false,
|
||||
statusCode: 0
|
||||
});
|
||||
});
|
||||
|
||||
test("entrypoint detection follows ConfigMap symlink paths", () => {
|
||||
const realScriptPath = "/var/run/configmaps/hwlab-v02-metrics-sidecar/..2026_06_05/metrics-sidecar.mjs";
|
||||
const mountedScriptPath = "/metrics/metrics-sidecar.mjs";
|
||||
|
||||
Reference in New Issue
Block a user