Let a job choose its AMS slot; soak test for telemetry
POST /jobs takes an optional amsSlot (stored in the job metadata) which dispatch passes to the gateway. The camera e2e script gains SOAK_S.
This commit is contained in:
@@ -148,6 +148,19 @@ if grep -qi 'access_code\|accessCode' "$WORK/backend.log"; then
|
|||||||
fi
|
fi
|
||||||
ok "the backend log never mentions an access code"
|
ok "the backend log never mentions an access code"
|
||||||
|
|
||||||
|
# Optional soak: the first real dispatch froze the proxy ~4.5 min in (an
|
||||||
|
# MQTT publish that never completed while the fleet lock was held). Staying
|
||||||
|
# connected well past that and still receiving telemetry is the regression test.
|
||||||
|
if [ -n "${SOAK_S:-}" ]; then
|
||||||
|
echo "==> soaking for ${SOAK_S}s: telemetry must keep arriving"
|
||||||
|
sleep "$SOAK_S"
|
||||||
|
AGE="$(psql_q "SELECT extract(epoch FROM now() - last_seen_at)::int FROM printers WHERE id='$CAM_UUID'")"
|
||||||
|
[ -n "$AGE" ] && [ "$AGE" -lt 20 ] || fail "telemetry is ${AGE:-never}s old after the soak - the proxy froze"
|
||||||
|
ok "telemetry still fresh (${AGE}s old) after ${SOAK_S}s"
|
||||||
|
code="$(fetch "$WORK/after.jpg")"; [ "$code" = 200 ] || fail "no camera frame after the soak: HTTP $code"
|
||||||
|
ok "camera still answers after the soak"
|
||||||
|
fi
|
||||||
|
|
||||||
[ -n "$SAVE_FRAME" ] && cp "$WORK/f2.jpg" "$SAVE_FRAME" && ok "kept a frame at $SAVE_FRAME"
|
[ -n "$SAVE_FRAME" ] && cp "$WORK/f2.jpg" "$SAVE_FRAME" && ok "kept a frame at $SAVE_FRAME"
|
||||||
echo
|
echo
|
||||||
echo "PASS - a P1/A1 camera frame travelled printer -> proxy -> backend -> an authenticated viewer."
|
echo "PASS - a P1/A1 camera frame travelled printer -> proxy -> backend -> an authenticated viewer."
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
PRINTER_ID="${PRINTER_ID:-p1p}"
|
PRINTER_ID="${PRINTER_ID:-p1p}"
|
||||||
FILE="${FILE:-auto-test-001.gcode.3mf}"
|
FILE="${FILE:-auto-test-001.gcode.3mf}"
|
||||||
|
# 0-based across all AMS units (AMS1 slot 4 is 3). Unset: map by the project's filament.
|
||||||
|
AMS_SLOT="${AMS_SLOT:-}"
|
||||||
PROXY_DIR="${PROXY_DIR:-$REPO_ROOT/../continuum-proxy}"
|
PROXY_DIR="${PROXY_DIR:-$REPO_ROOT/../continuum-proxy}"
|
||||||
GO2RTC_BIN="${GO2RTC_BIN:-$HOME/.local/bin/go2rtc}"
|
GO2RTC_BIN="${GO2RTC_BIN:-$HOME/.local/bin/go2rtc}"
|
||||||
PORT="${PORT:-3060}"; AUTH_PORT="${AUTH_PORT:-3061}"
|
PORT="${PORT:-3060}"; AUTH_PORT="${AUTH_PORT:-3061}"
|
||||||
@@ -107,7 +109,7 @@ ok "the cloud sees $PRINTER_ID idle"
|
|||||||
|
|
||||||
echo "==> queueing the job"
|
echo "==> queueing the job"
|
||||||
J="$(curl -sf -X POST "$BASE/jobs" -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
|
J="$(curl -sf -X POST "$BASE/jobs" -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
|
||||||
-d "{\"farmId\":\"$FARM_ID\",\"fileName\":\"$FILE\",\"contentType\":\"application/octet-stream\"}")"
|
-d "{\"farmId\":\"$FARM_ID\",\"fileName\":\"$FILE\",\"contentType\":\"application/octet-stream\"${AMS_SLOT:+,\"amsSlot\":$AMS_SLOT}}")"
|
||||||
JOB_ID="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["id"])')"
|
JOB_ID="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["id"])')"
|
||||||
OBJECT_KEY="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["r2ObjectKey"])')"
|
OBJECT_KEY="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["job"]["r2ObjectKey"])')"
|
||||||
UPLOAD_URL="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["uploadUrl"])')"
|
UPLOAD_URL="$(printf '%s' "$J" | python3 -c 'import json,sys; print(json.load(sys.stdin)["uploadUrl"])')"
|
||||||
|
|||||||
@@ -213,6 +213,8 @@ export async function dispatchTick(): Promise<number> {
|
|||||||
job_id: job.id,
|
job_id: job.id,
|
||||||
file_name: job.fileName,
|
file_name: job.fileName,
|
||||||
url,
|
url,
|
||||||
|
// Chosen by whoever queued the job; absent means "by the project's filament".
|
||||||
|
...(typeof job.metadata?.amsSlot === "number" ? { ams_slot: job.metadata.amsSlot } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const jobRoutes = new Elysia({ prefix: "/jobs" })
|
|||||||
farmId: body.farmId,
|
farmId: body.farmId,
|
||||||
fileName: body.fileName,
|
fileName: body.fileName,
|
||||||
r2ObjectKey,
|
r2ObjectKey,
|
||||||
|
metadata: body.amsSlot === undefined ? {} : { amsSlot: body.amsSlot },
|
||||||
// From the verified token, never from the body. It used to be a
|
// From the verified token, never from the body. It used to be a
|
||||||
// `submittedBy` field the caller filled in, which made the audit
|
// `submittedBy` field the caller filled in, which made the audit
|
||||||
// trail a suggestion rather than a record.
|
// trail a suggestion rather than a record.
|
||||||
@@ -89,6 +90,9 @@ export const jobRoutes = new Elysia({ prefix: "/jobs" })
|
|||||||
farmId: t.String({ format: "uuid" }),
|
farmId: t.String({ format: "uuid" }),
|
||||||
fileName: t.String({ minLength: 1, maxLength: 500 }),
|
fileName: t.String({ minLength: 1, maxLength: 500 }),
|
||||||
contentType: t.Optional(t.String()),
|
contentType: t.Optional(t.String()),
|
||||||
|
// 0-based across all AMS units (AMS1 slot 4 is 3). Absent: the
|
||||||
|
// gateway maps by the project's own filament.
|
||||||
|
amsSlot: t.Optional(t.Integer({ minimum: 0, maximum: 15 })),
|
||||||
}),
|
}),
|
||||||
detail: { tags: ["jobs"], summary: "Queue a print job and get an upload URL" },
|
detail: { tags: ["jobs"], summary: "Queue a print job and get an upload URL" },
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user