test: add remote runner vagrant scenario
This commit is contained in:
@@ -7,3 +7,6 @@ This scenario verifies the first concrete uptime.link runner flow:
|
||||
3. The runner polls `/api/runner/v1/checks`.
|
||||
4. The runner executes the assigned HTTP check locally.
|
||||
5. The runner posts the result to `/api/runner/v1/results`.
|
||||
6. The scenario verifies scheduling, status derivation, snapshot restore,
|
||||
unauthorized access rejection, token rotation, disabled runner rejection, and
|
||||
label updates.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { assert, assertEquals } from "jsr:@std/assert@^1.0.0";
|
||||
|
||||
import { RunnerAdmin } from "../../../uptime.link/ts_api/classes/runner-admin.ts";
|
||||
import { RunnerCoordinator } from "../../../uptime.link/ts_api/classes/runner-coordinator.ts";
|
||||
import { createRunnerRequestHandler } from "../../../uptime.link/ts_api/classes/runner-request-handler.ts";
|
||||
|
||||
const scenarioName = "uptimerunner-vagrant";
|
||||
const controllerHost = Deno.env.get("UPTIMELINK_CONTROLLER_HOST") ?? "0.0.0.0";
|
||||
const controllerPort = Number(
|
||||
Deno.env.get("UPTIMELINK_CONTROLLER_PORT") ?? "8080",
|
||||
);
|
||||
const runnerId = Deno.env.get("UPTIMELINK_RUNNER_ID") ?? "vagrant-runner-1";
|
||||
const runnerToken = Deno.env.get("UPTIMELINK_RUNNER_TOKEN") ?? "vagrant-token";
|
||||
const targetPort = Number(Deno.env.get("UPTIMELINK_TARGET_PORT") ?? "18081");
|
||||
const timeoutMs = Number(
|
||||
Deno.env.get("UPTIMELINK_VAGRANT_TIMEOUT_MS") ?? "120000",
|
||||
);
|
||||
const targetUrl = Deno.env.get("UPTIMELINK_RUNNER_TARGET_URL") ??
|
||||
`http://127.0.0.1:${targetPort}/health`;
|
||||
|
||||
const main = async () => {
|
||||
const coordinator = new RunnerCoordinator();
|
||||
const admin = new RunnerAdmin(coordinator);
|
||||
admin.registerRunner({
|
||||
runnerId,
|
||||
token: runnerToken,
|
||||
labels: ["scenario:vagrant", "role:internal"],
|
||||
});
|
||||
|
||||
coordinator.enqueueCheck({
|
||||
id: "vagrant-runner-local-http",
|
||||
type: "http",
|
||||
url: targetUrl,
|
||||
expectedStatusCodes: [200],
|
||||
expectedBodyIncludes: "runner-local healthy",
|
||||
timeoutMs: 5000,
|
||||
metadata: {
|
||||
monitorId: "vagrant-runner-local-http",
|
||||
},
|
||||
}, { runnerId });
|
||||
|
||||
const runnerRequestHandler = createRunnerRequestHandler(coordinator);
|
||||
const server = Deno.serve(
|
||||
{ hostname: controllerHost, port: controllerPort },
|
||||
(request) => {
|
||||
return runnerRequestHandler(request);
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[${scenarioName}] Controller listening on ${controllerHost}:${controllerPort}`,
|
||||
);
|
||||
console.log(`[${scenarioName}] Waiting for runner ${runnerId}`);
|
||||
|
||||
await waitFor(
|
||||
() => coordinator.getRunner(runnerId)?.lastHeartbeat,
|
||||
timeoutMs,
|
||||
);
|
||||
const result = await waitFor(
|
||||
() =>
|
||||
coordinator.listResults().find((candidate) => {
|
||||
return candidate.checkId === "vagrant-runner-local-http";
|
||||
}),
|
||||
timeoutMs,
|
||||
);
|
||||
|
||||
assert(result);
|
||||
if (result.status !== "ok") {
|
||||
console.error(JSON.stringify(result, null, 2));
|
||||
}
|
||||
assertEquals(result.runnerId, runnerId);
|
||||
assertEquals(result.type, "http");
|
||||
assertEquals(result.status, "ok");
|
||||
assertEquals(result.statusCode, 200);
|
||||
assert(result.responseTime !== undefined);
|
||||
assertEquals(coordinator.getQueueLength(), 0);
|
||||
|
||||
console.log(`[${scenarioName}] Passed`);
|
||||
} finally {
|
||||
await server.shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
async function waitFor<T>(
|
||||
readArg: () => T | undefined,
|
||||
timeoutMsArg: number,
|
||||
): Promise<T> {
|
||||
const started = Date.now();
|
||||
while (Date.now() - started < timeoutMsArg) {
|
||||
const value = readArg();
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
throw new Error(`Timed out after ${timeoutMsArg}ms.`);
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[${scenarioName}] Failed: ${
|
||||
error instanceof Error ? error.stack : String(error)
|
||||
}`,
|
||||
);
|
||||
Deno.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# uptimerunner-vagrant
|
||||
|
||||
This scenario verifies a real remote runner shape with two Vagrant machines:
|
||||
|
||||
1. `controller` runs the uptime.link runner protocol handler.
|
||||
2. `runner` compiles and installs `uptimerunner` as a systemd service.
|
||||
3. `runner` also runs a local HTTP target on `127.0.0.1`.
|
||||
4. The controller assigns a check for that runner-local target.
|
||||
5. The remote runner heartbeats, executes the check from its own VM, and submits
|
||||
the result back to the controller.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
pnpm scenario:uptimerunner-vagrant
|
||||
```
|
||||
|
||||
Override the default private-network addresses if they collide locally:
|
||||
|
||||
```bash
|
||||
UPTIMELINK_VAGRANT_CONTROLLER_IP=192.168.60.10 \
|
||||
UPTIMELINK_VAGRANT_RUNNER_IP=192.168.60.11 \
|
||||
pnpm scenario:uptimerunner-vagrant
|
||||
```
|
||||
|
||||
The scenario defaults to `UPTIMELINK_VAGRANT_PROVIDER=libvirt`. If this host
|
||||
uses VirtualBox instead, run:
|
||||
|
||||
```bash
|
||||
UPTIMELINK_VAGRANT_PROVIDER=virtualbox pnpm scenario:uptimerunner-vagrant
|
||||
```
|
||||
|
||||
If the current shell has not picked up newly added `libvirt` group membership,
|
||||
run:
|
||||
|
||||
```bash
|
||||
sg libvirt -c 'pnpm scenario:uptimerunner-vagrant'
|
||||
```
|
||||
Reference in New Issue
Block a user