|
| 1 | +import type { ChaosProvider } from "@chaos/provider"; |
| 2 | +import type { DockerContainer } from "network-stability/container"; |
| 3 | + |
| 4 | +export type NetworkChaosConfig = { |
| 5 | + delayMin: number; // Minimum delay in ms |
| 6 | + delayMax: number; // Maximum delay in ms |
| 7 | + jitterMin: number; // Minimum jitter in ms |
| 8 | + jitterMax: number; // Maximum jitter in ms |
| 9 | + lossMin: number; // Minimum packet loss percentage (0-100) |
| 10 | + lossMax: number; // Maximum packet loss percentage (0-100) |
| 11 | + interval: number; // How often to apply chaos in ms |
| 12 | +}; |
| 13 | + |
| 14 | +// import type { Worker } from "@workers/manager"; |
| 15 | + |
| 16 | +export class NetworkChaos implements ChaosProvider { |
| 17 | + config: NetworkChaosConfig; |
| 18 | + interval?: NodeJS.Timeout; |
| 19 | + nodes: DockerContainer[]; |
| 20 | + |
| 21 | + constructor(config: NetworkChaosConfig, nodes: DockerContainer[]) { |
| 22 | + this.config = config; |
| 23 | + this.nodes = nodes; |
| 24 | + } |
| 25 | + |
| 26 | + start(): Promise<void> { |
| 27 | + console.log(`Starting network chaos: |
| 28 | + Nodes: ${this.nodes.map((node) => node.name).join(", ")} |
| 29 | + Delay: ${this.config.delayMin}ms - ${this.config.delayMax}ms |
| 30 | + Jitter: ${this.config.jitterMin}ms - ${this.config.jitterMax}ms |
| 31 | + Loss: ${this.config.lossMin}% - ${this.config.lossMax}% |
| 32 | + Interval: ${this.config.interval}ms`); |
| 33 | + |
| 34 | + validateContainers(this.nodes); |
| 35 | + |
| 36 | + this.interval = setInterval(() => { |
| 37 | + for (const node of this.nodes) { |
| 38 | + this.applyToNode(node); |
| 39 | + } |
| 40 | + }, this.config.interval); |
| 41 | + |
| 42 | + return Promise.resolve(); |
| 43 | + } |
| 44 | + |
| 45 | + private applyToNode(node: DockerContainer) { |
| 46 | + const { delayMin, delayMax, jitterMin, jitterMax, lossMin, lossMax } = |
| 47 | + this.config; |
| 48 | + const delay = Math.floor(delayMin + Math.random() * (delayMax - delayMin)); |
| 49 | + const jitter = Math.floor( |
| 50 | + jitterMin + Math.random() * (jitterMax - jitterMin), |
| 51 | + ); |
| 52 | + const loss = lossMin + Math.random() * (lossMax - lossMin); |
| 53 | + |
| 54 | + try { |
| 55 | + node.addJitter(delay, jitter); |
| 56 | + node.addLoss(loss); |
| 57 | + } catch (err) { |
| 58 | + console.warn(`[chaos] Error applying netem on ${node.name}:`, err); |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + stop(): Promise<void> { |
| 63 | + if (this.interval) { |
| 64 | + clearInterval(this.interval); |
| 65 | + } |
| 66 | + |
| 67 | + for (const node of this.nodes) { |
| 68 | + try { |
| 69 | + node.clearLatency(); |
| 70 | + } catch (err) { |
| 71 | + console.warn(`[chaos] Error clearing latency on ${node.name}:`, err); |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + return Promise.resolve(); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +const validateContainers = (allNodes: DockerContainer[]) => { |
| 80 | + for (const node of allNodes) { |
| 81 | + try { |
| 82 | + // Test if container exists by trying to get its IP |
| 83 | + if (!node.ip || !node.veth) { |
| 84 | + throw new Error(`Container ${node.name} has no IP address`); |
| 85 | + } |
| 86 | + } catch { |
| 87 | + throw new Error( |
| 88 | + `Docker container ${node.name} is not running. Network chaos requires local multinode setup (./dev/up).`, |
| 89 | + ); |
| 90 | + } |
| 91 | + } |
| 92 | +}; |
0 commit comments