localstack-chaos-injector
Inject and manage chaos faults and network latency in LocalStack to test AWS service resilience during development.
Instructions
Injects, manages, and clears chaos faults and network effects in LocalStack to test system resilience.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | The specific chaos engineering action to perform. | |
| rules | No | An array of fault rules. Required for 'inject-faults', 'add-fault-rule', and 'remove-fault-rule' actions. | |
| latency_ms | No | Network latency in milliseconds. Required for the 'inject-latency' action. |
Implementation Reference
- The primary handler function for the 'localstack-chaos-injector' tool. It processes input parameters, performs preflight checks, initializes ChaosApiClient, and dispatches to specific action handlers via a switch statement (e.g., inject-faults, add-fault-rule, inject-latency).export default async function localstackChaosInjector({ action, rules, latency_ms, }: InferSchema<typeof schema>) { const preflightError = await runPreflights([requireProFeature(ProFeature.CHAOS_ENGINEERING)]); if (preflightError) return preflightError; const client = new ChaosApiClient(); switch (action) { case "get-faults": { const result = await client.getFaults(); if (!result.success) { return { content: [{ type: "text", text: result.message }] }; } const formattedRules = formatFaultRules(result.data); return { content: [{ type: "text", text: formattedRules }] }; } case "clear-all-faults": { const result = await client.setFaults([]); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } return ResponseBuilder.success( "All chaos faults have been cleared. The system is now operating normally." ); } case "inject-faults": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `inject-faults` action requires the `rules` parameter to be specified.", }, ], }; } const setResult = await client.setFaults(rules); if (!setResult.success) { return ResponseBuilder.error("Chaos API Error", setResult.message); } // Get current state to confirm const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ New chaos faults have been injected (overwriting any previous rules). The current active faults are: ${formatFaultRules(getCurrentResult.data)}`; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } case "add-fault-rule": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `add-fault-rule` action requires the `rules` parameter to be specified.", }, ], }; } const addResult = await client.addFaultRules(rules); if (!addResult.success) { return ResponseBuilder.error("Chaos API Error", addResult.message); } // Get current state to confirm const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ New fault rule(s) have been added. The current active faults are: ${formatFaultRules(getCurrentResult.data)}`; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } case "remove-fault-rule": { if (!rules || rules.length === 0) { return { content: [ { type: "text", text: "❌ **Error:** The `remove-fault-rule` action requires the `rules` parameter to be specified.", }, ], }; } // First get current rules to check if the rule exists const getCurrentResult = await client.getFaults(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } // Check if all rules to remove exist in current configuration const currentRules = getCurrentResult.data || []; const rulesToRemove = rules; for (const ruleToRemove of rulesToRemove) { const ruleExists = currentRules.some((currentRule: any) => rulesMatch(currentRule, ruleToRemove) ); if (!ruleExists) { return ResponseBuilder.markdown(`⚠️ The specified rule was not found in the current configuration. No changes were made. Current configuration: ${formatFaultRules(currentRules)}`); } } // Rule exists, proceed with removal const removeResult = await client.removeFaultRules(rulesToRemove); if (!removeResult.success) { return ResponseBuilder.error("Chaos API Error", removeResult.message); } // Get current state after removal to confirm const getUpdatedResult = await client.getFaults(); if (!getUpdatedResult.success) { return ResponseBuilder.error("Chaos API Error", getUpdatedResult.message); } const message = `✅ The specified fault rule(s) have been removed. The current active faults are: ${formatFaultRules(getUpdatedResult.data)}`; return ResponseBuilder.markdown(message); } case "get-latency": { const result = await client.getEffects(); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } const latency = (result.data as any)?.latency || 0; return ResponseBuilder.markdown(`The current network latency is ${latency}ms.`); } case "clear-latency": { const result = await client.setEffects({ latency: 0 }); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } // Get current state to confirm const getCurrentResult = await client.getEffects(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ Network latency has been cleared. The current effects are: \`\`\`json ${JSON.stringify(getCurrentResult.data, null, 2)} \`\`\``; return ResponseBuilder.markdown(message); } case "inject-latency": { if (latency_ms === undefined || latency_ms === null) { return { content: [ { type: "text", text: "❌ **Error:** The `inject-latency` action requires the `latency_ms` parameter to be specified.", }, ], }; } const result = await client.setEffects({ latency: latency_ms }); if (!result.success) { return ResponseBuilder.error("Chaos API Error", result.message); } // Get current state to confirm const getCurrentResult = await client.getEffects(); if (!getCurrentResult.success) { return ResponseBuilder.error("Chaos API Error", getCurrentResult.message); } const message = `✅ Latency of ${latency_ms}ms has been injected. The current network effects are: \`\`\`json ${JSON.stringify(getCurrentResult.data, null, 2)} \`\`\``; return ResponseBuilder.markdown(addWorkflowGuidance(message)); } default: return ResponseBuilder.error("Unknown action", `Unsupported action: ${action}`); } }
- Zod schema defining the input parameters for the tool, including 'action', 'rules' (array of fault rules), and 'latency_ms'.export const schema = { action: z .enum([ "inject-faults", "add-fault-rule", "remove-fault-rule", "get-faults", "clear-all-faults", "inject-latency", "get-latency", "clear-latency", ]) .describe("The specific chaos engineering action to perform."), rules: z .array(faultRuleSchema) .optional() .describe( "An array of fault rules. Required for 'inject-faults', 'add-fault-rule', and 'remove-fault-rule' actions." ), latency_ms: z .number() .int() .min(0) .optional() .describe("Network latency in milliseconds. Required for the 'inject-latency' action."), };
- src/tools/localstack-chaos-injector.ts:74-84 (registration)Tool metadata export that registers the tool with name 'localstack-chaos-injector', description, and annotations indicating it's destructive.export const metadata: ToolMetadata = { name: "localstack-chaos-injector", description: "Injects, manages, and clears chaos faults and network effects in LocalStack to test system resilience.", annotations: { title: "LocalStack Chaos Injector", readOnlyHint: false, destructiveHint: true, idempotentHint: false, }, };
- Helper function to check if two fault rules match exactly, used in remove-fault-rule logic.function rulesMatch(rule1: any, rule2: any): boolean { const keys1 = Object.keys(rule1).sort(); const keys2 = Object.keys(rule2).sort(); if (keys1.length !== keys2.length) return false; if (keys1.join(",") !== keys2.join(",")) return false; for (const key of keys1) { if (typeof rule1[key] === "object" && typeof rule2[key] === "object") { if (!rulesMatch(rule1[key], rule2[key])) return false; } else if (rule1[key] !== rule2[key]) { return false; } } return true; }
- Zod schema for individual fault rules, used within the main schema's 'rules' array.const faultRuleSchema = z .object({ service: z .string() .optional() .describe("Name of the AWS service to affect (e.g., 's3', 'lambda')."), region: z.string().optional().describe("Name of the AWS region to affect (e.g., 'us-east-1')."), operation: z .string() .optional() .describe("Name of the specific service operation to affect (e.g., 'CreateBucket')."), probability: z .number() .min(0) .max(1) .optional() .describe("The probability (0.0 to 1.0) of the fault occurring."), error: z .object({ statusCode: z .number() .int() .optional() .describe("The HTTP status code to return (e.g., 503)."), code: z .string() .optional() .describe("The AWS error code to return (e.g., 'ServiceUnavailable')."), }) .optional() .describe("The custom error to return."), }) .describe("A single rule defining a chaos fault.");