Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion scripts/runtime-faults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "vitest";
import { applyFault, type FaultRuntime } from "./runtime-faults.ts";
import { netemOptions } from "./runtime-network.ts";
import { netemOptions, parseNetworkPath } from "./runtime-network.ts";

function faultRuntime(overrides: Partial<FaultRuntime>): FaultRuntime {
return {
Expand Down Expand Up @@ -77,6 +77,10 @@ test("a partial blackhole restores applied rules and retains cleanup failures",
});

test("network impairment inputs reject malformed or out-of-range values", () => {
assert.equal(parseNetworkPath("forwarding"), "forwarding");
assert.equal(parseNetworkPath("publisher"), "publisher");
assert.throws(() => parseNetworkPath(undefined), /invalid network path/);
assert.throws(() => parseNetworkPath("other"), /invalid network path/);
assert.deepEqual(netemOptions("latency", "20ms", undefined, "7"), ["delay", "10ms", "seed", "7"]);
assert.deepEqual(netemOptions("packet-loss", undefined, "0.1", "0"), ["loss", "random", "0.1%"]);
assert.throws(
Expand Down
3 changes: 2 additions & 1 deletion scripts/runtime-faults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
impairmentEndpoints,
netemOptions,
publisherServices,
type NetworkPath,
type RuntimeService,
} from "./runtime-network.ts";
import { parseJSON } from "./validation.ts";
Expand Down Expand Up @@ -44,7 +45,7 @@ export type FaultRuntime = {
readonly ingresses: readonly ("ingress-a" | "ingress-b")[];
readonly activePublishers: readonly (typeof publisherServices)[number][];
readonly network: {
readonly path: string | undefined;
readonly path: NetworkPath;
readonly rtt: string | undefined;
readonly loss: string | undefined;
readonly seed: string | undefined;
Expand Down
11 changes: 7 additions & 4 deletions scripts/runtime-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { join, resolve } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import * as z from "zod";
import { applyFault, faultSchema, type FaultEvents, type FaultRuntime } from "./runtime-faults.ts";
import { publisherServices } from "./runtime-network.ts";
import { parseNetworkPath, publisherServices } from "./runtime-network.ts";
import { parseJSON } from "./validation.ts";

const containerSchema = z.object({
Expand Down Expand Up @@ -36,7 +36,10 @@ const routes = z.coerce
.max(10_000)
.parse(process.env.PUBLIC_URLS ?? "4");
const haTopology = z.enum(["0", "1"]).parse(process.env.HA_TOPOLOGY ?? "1") === "1";
const replicatedRelays = process.env.SCENARIO === "control-restart";
const scenario = z
.union([z.enum(["relay-restart", "control-restart"]), faultSchema])
.parse(process.env.SCENARIO ?? "relay-restart");
const replicatedRelays = scenario === "control-restart";
// each publisher component owns striped pairs of public URLs. small smoke runs
// intentionally leave some publisher components without a public URL.
const activePublishers = publisherServices.filter((_, shard) => shard * 2 < routes);
Expand Down Expand Up @@ -79,7 +82,7 @@ let faultError: unknown;
const faultState = { intentionalRelayExit: false };
let faultStarted = false;
let interrupted = false;
const externalFault = faultSchema.safeParse(process.env.SCENARIO).success;
const externalFault = faultSchema.safeParse(scenario).success;
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"])
process.on(signal, () => {
interrupted = true;
Expand Down Expand Up @@ -208,7 +211,7 @@ try {
ingresses,
activePublishers,
network: {
path: process.env.NETWORK_PATH,
path: parseNetworkPath(process.env.NETWORK_PATH ?? "forwarding"),
rtt: process.env.RTT,
loss: process.env.LOSS,
seed: process.env.SEED,
Expand Down
19 changes: 11 additions & 8 deletions scripts/runtime-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const lossSchema = z.enum(["0.1", "1"]);
const seedSchema = z.coerce.number().pipe(z.int().min(0).max(0xffffffff));

export type ImpairmentScenario = "latency" | "packet-loss";
export type NetworkPath = "forwarding" | "publisher";
export type NetworkPath = z.infer<typeof pathSchema>;
export const publisherServices = [
"publishers",
"publishers-2",
Expand All @@ -27,18 +27,21 @@ export interface ImpairmentEndpoint {
readonly service: RuntimeService;
}

export function parseNetworkPath(value: string | undefined): NetworkPath {
const selected = pathSchema.safeParse(value);
if (!selected.success) throw new Error("invalid network path");
return selected.data;
}

export function impairmentEndpoints(
path: string | undefined,
path: NetworkPath,
addresses: Readonly<Record<RuntimeService, string>>,
ingresses: readonly ("ingress-a" | "ingress-b")[],
activePublishers: readonly (typeof publisherServices)[number][],
): [ImpairmentEndpoint, ...ImpairmentEndpoint[]] {
const selected = pathSchema.safeParse(path);
if (!selected.success) throw new Error("invalid network path");
const sources: readonly RuntimeService[] =
selected.data === "forwarding" ? ingresses : activePublishers;
const port = selected.data === "forwarding" ? "8443" : "443";
const protocols = selected.data === "forwarding" ? ["6"] : ["6", "17"];
const sources: readonly RuntimeService[] = path === "forwarding" ? ingresses : activePublishers;
const port = path === "forwarding" ? "8443" : "443";
const protocols = path === "forwarding" ? ["6"] : ["6", "17"];
const forwards = sources.map((service): ImpairmentEndpoint => ({ service, filters: [] }));
const first = forwards[0];
if (!first) throw new Error("network path requires a source");
Expand Down
Loading