fix: list multiple device pod executors

This commit is contained in:
Codex
2026-05-29 19:26:58 +08:00
parent b6e94770c4
commit 6289978cd4
2 changed files with 64 additions and 11 deletions
+35
View File
@@ -106,6 +106,41 @@ test("device pod executor exposes cloud-api authority boundary and method guards
}
});
test("device pod executor lists and isolates multiple logical device pods", async () => {
const service = await startDevicePod("device-pod-main", {
HWLAB_DEVICE_POD_IDS: "device-pod-main, device-pod-alt"
});
try {
const list = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods`);
assert.equal(list.selectedDevicePodId, "device-pod-main");
assert.deepEqual(list.devicePods.map((pod) => pod.devicePodId), ["device-pod-main", "device-pod-alt"]);
assert.equal(list.devicePods[1].routes.internalJob, "/v1/device-pods/device-pod-alt/jobs");
const mainJobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-main/jobs`, {
method: "POST",
headers: { "content-type": "application/json", ...internalHeaders() },
body: JSON.stringify({ jobId: "job_multi_main", intent: "workspace.ls", traceId: "trc_multi_main", operationId: "op_multi_main" })
});
assert.equal(mainJobResponse.status, 409);
const altJobResponse = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs`, {
method: "POST",
headers: { "content-type": "application/json", ...internalHeaders() },
body: JSON.stringify({ jobId: "job_multi_alt", intent: "workspace.ls", traceId: "trc_multi_alt", operationId: "op_multi_alt" })
});
assert.equal(altJobResponse.status, 409);
const mainJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-main/jobs/job_multi_main`, { headers: internalHeaders() });
assert.equal(mainJob.job.devicePodId, "device-pod-main");
const altJob = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs/job_multi_alt`, { headers: internalHeaders() });
assert.equal(altJob.job.devicePodId, "device-pod-alt");
const crossRead = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-alt/jobs/job_multi_main`, { headers: internalHeaders() });
assert.equal(crossRead.status, 404);
} finally {
await service.stop();
}
});
test("device-pod executor bounds gateway output text", async () => {
const longText = "z".repeat(13000);
const cloudApi = createServer(async (request, response) => {
+29 -11
View File
@@ -9,6 +9,7 @@ const CONTRACT_VERSION = "device-pod-executor-v1";
const DEFAULT_DEVICE_POD_ID = "device-pod-71-freq";
const port = parsePort(process.env.HWLAB_DEVICE_POD_PORT, parsePort(process.env.PORT, 7601));
const devicePodId = process.env.HWLAB_DEVICE_POD_ID || DEFAULT_DEVICE_POD_ID;
const devicePodIds = configuredDevicePodIds(process.env.HWLAB_DEVICE_POD_IDS, devicePodId);
const environment = process.env.HWLAB_ENVIRONMENT || process.env.HWLAB_GITOPS_PROFILE || "dev";
const cloudApiInternalUrl = normalizeBaseUrl(process.env.HWLAB_CLOUD_API_INTERNAL_URL || process.env.HWLAB_CLOUD_API_URL);
const internalToken = textOr(process.env.HWLAB_DEVICE_POD_INTERNAL_TOKEN, "");
@@ -70,6 +71,7 @@ function healthPayload() {
ready: true,
contractVersion: CONTRACT_VERSION,
devicePodId,
devicePodIds,
observedAt: new Date().toISOString(),
role: "device-pod-internal-executor",
authority: "hwlab-cloud-api",
@@ -88,17 +90,21 @@ function listPayload() {
status: "ok",
source: sourcePayload(),
selectedDevicePodId: devicePodId,
devicePods: [{
devicePodId,
status: "executor-ready",
authority: "hwlab-cloud-api",
fake: false,
gatewayAdapter: gatewayAdapterState(),
routes: {
cloudAuthority: "/v1/device-pods",
internalJob: `/v1/device-pods/${encodeURIComponent(devicePodId)}/jobs`
}
}]
devicePods: devicePodIds.map((id) => executorDevicePodSummary(id))
};
}
function executorDevicePodSummary(id) {
return {
devicePodId: id,
status: "executor-ready",
authority: "hwlab-cloud-api",
fake: false,
gatewayAdapter: gatewayAdapterState(),
routes: {
cloudAuthority: "/v1/device-pods",
internalJob: `/v1/device-pods/${encodeURIComponent(id)}/jobs`
}
};
}
@@ -513,6 +519,18 @@ function normalizeBaseUrl(value) {
return text ? text.replace(/\/+$/u, "") : "";
}
function configuredDevicePodIds(value, fallbackId) {
const ids = String(value ?? "")
.split(",")
.map((item) => item.trim())
.filter(Boolean);
const unique = [];
for (const id of [fallbackId, ...ids]) {
if (!unique.includes(id)) unique.push(id);
}
return unique;
}
function jobOutputPayload(job) {
return {
...jobPayload(job),