Spaces:
Sleeping
Sleeping
Harikishanth R commited on
Commit Β·
ff31e01
1
Parent(s): 1e9f0f1
feat: docker-compose + eliminate ALL HTTP flags
Browse files- docker-compose.yml: 16 real containers on bridge network (172.20.0.x)
- Dockerfile.service: lightweight per-service container
- ALL 25 faults now have OS-level component:
cache_invalidation: file deletion + SIGSTOP
webhook_storm: real HTTP request flood via threads
index_corruption: random bytes to index file
index_lag: 1200-doc physical backlog file
stale_entries: poisoned DNS cache file
all_backends_removed: SIGSTOP loadbalancer
session_corruption: DB row corruption
scrape_failure: SIGSTOP metrics_collector
retention_full: 5MB junk file
email_queue_overflow: 500-msg physical backlog
billing_desync: ghost charges in DB
invoice_stuck: SIGSTOP billing
config_poisoned: poisoned config.json file
- Dockerfile.service +34 -0
- diag_colab_hf.py +224 -0
- docker-compose.yml +231 -0
- services/orchestrator.py +176 -28
Dockerfile.service
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CloudSRE v2 β Lightweight Service Container
|
| 2 |
+
# Each of the 16 microservices runs in its own container from this image.
|
| 3 |
+
# Shared volumes (/data, /var/log) provide cross-service communication
|
| 4 |
+
# just like real cloud infrastructure (shared RDS, shared CloudWatch).
|
| 5 |
+
|
| 6 |
+
FROM python:3.11-slim
|
| 7 |
+
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
# Install only runtime dependencies (no build tools)
|
| 11 |
+
RUN pip install --no-cache-dir \
|
| 12 |
+
fastapi==0.115.* \
|
| 13 |
+
uvicorn[standard]==0.34.* \
|
| 14 |
+
httpx==0.28.* \
|
| 15 |
+
pydantic==2.* \
|
| 16 |
+
&& apt-get update && apt-get install -y --no-install-recommends curl \
|
| 17 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
# Copy service code
|
| 20 |
+
COPY services/ /app/cloud_sre_v2/services/
|
| 21 |
+
COPY infra/ /app/cloud_sre_v2/infra/
|
| 22 |
+
COPY models.py /app/cloud_sre_v2/models.py
|
| 23 |
+
COPY __init__.py /app/cloud_sre_v2/__init__.py
|
| 24 |
+
|
| 25 |
+
# Create log directories for all services
|
| 26 |
+
RUN mkdir -p /var/log/payment /var/log/auth /var/log/worker /var/log/frontend \
|
| 27 |
+
/var/log/cache /var/log/notification /var/log/search /var/log/gateway \
|
| 28 |
+
/var/log/scheduler /var/log/storage /var/log/metrics_collector /var/log/email \
|
| 29 |
+
/var/log/billing /var/log/config /var/log/dns /var/log/loadbalancer /data
|
| 30 |
+
|
| 31 |
+
ENV PYTHONPATH="/app:$PYTHONPATH"
|
| 32 |
+
|
| 33 |
+
# Default: overridden by docker-compose command
|
| 34 |
+
CMD ["python", "-m", "cloud_sre_v2.services._service_worker", "--help"]
|
diag_colab_hf.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
CloudSRE v2 β Colab/HF Space training diagnostic smoke.
|
| 3 |
+
|
| 4 |
+
Static-only checks (no subprocess spawn) that surface issues that will bite
|
| 5 |
+
during training on Colab notebooks or HuggingFace Spaces. Writes NDJSON
|
| 6 |
+
runtime evidence to <workspace>/debug-4e9608.log.
|
| 7 |
+
|
| 8 |
+
Run from workspace root:
|
| 9 |
+
python cloud_sre_v2/diag_colab_hf.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
# region agent log helpers
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import re
|
| 16 |
+
import sys
|
| 17 |
+
import time
|
| 18 |
+
import shutil
|
| 19 |
+
import inspect
|
| 20 |
+
import tempfile
|
| 21 |
+
import traceback
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
|
| 24 |
+
WORKSPACE = Path(__file__).resolve().parent.parent
|
| 25 |
+
LOG_PATH = WORKSPACE / "debug-4e9608.log"
|
| 26 |
+
SESSION = "4e9608"
|
| 27 |
+
RUN_ID = "diag_initial"
|
| 28 |
+
|
| 29 |
+
if str(WORKSPACE) not in sys.path:
|
| 30 |
+
sys.path.insert(0, str(WORKSPACE))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _log(hypothesis_id: str, location: str, message: str, data: dict) -> None:
|
| 34 |
+
entry = {
|
| 35 |
+
"sessionId": SESSION,
|
| 36 |
+
"id": f"log_{int(time.time() * 1000)}_{hypothesis_id}",
|
| 37 |
+
"timestamp": int(time.time() * 1000),
|
| 38 |
+
"location": location,
|
| 39 |
+
"message": message,
|
| 40 |
+
"data": data,
|
| 41 |
+
"hypothesisId": hypothesis_id,
|
| 42 |
+
"runId": RUN_ID,
|
| 43 |
+
}
|
| 44 |
+
with open(LOG_PATH, "a", encoding="utf-8") as f:
|
| 45 |
+
f.write(json.dumps(entry, default=str) + "\n")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _safe(test_id: str, location: str, fn):
|
| 49 |
+
try:
|
| 50 |
+
fn()
|
| 51 |
+
except Exception as exc:
|
| 52 |
+
_log(test_id, location, "TEST_RAISED",
|
| 53 |
+
{"error_type": type(exc).__name__,
|
| 54 |
+
"error": str(exc)[:400],
|
| 55 |
+
"traceback": traceback.format_exc()[:1200]})
|
| 56 |
+
# endregion
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# region agent log H1 β FastAPI /health route registration
|
| 60 |
+
def test_h1_health_route():
|
| 61 |
+
from cloud_sre_v2.server.app import app
|
| 62 |
+
routes = []
|
| 63 |
+
for r in app.routes:
|
| 64 |
+
path = getattr(r, "path", None)
|
| 65 |
+
methods = sorted(getattr(r, "methods", []) or [])
|
| 66 |
+
if path:
|
| 67 |
+
routes.append({"path": path, "methods": methods})
|
| 68 |
+
paths = [r["path"] for r in routes]
|
| 69 |
+
_log("H1", "diag_colab_hf.py:test_h1_health_route",
|
| 70 |
+
"FastAPI route inventory after create_app",
|
| 71 |
+
{"total_routes": len(routes),
|
| 72 |
+
"has_/health": "/health" in paths,
|
| 73 |
+
"has_/healthz": "/healthz" in paths,
|
| 74 |
+
"has_/reset": "/reset" in paths,
|
| 75 |
+
"has_/step": "/step" in paths,
|
| 76 |
+
"has_/state": "/state" in paths,
|
| 77 |
+
"has_/tasks": "/tasks" in paths,
|
| 78 |
+
"all_paths": sorted(set(paths))})
|
| 79 |
+
# endregion
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# region agent log H2 β SFT training data covers new services
|
| 83 |
+
def test_h2_sft_coverage():
|
| 84 |
+
sft_path = WORKSPACE / "cloud_sre_v2" / "sft_training_data.jsonl"
|
| 85 |
+
if not sft_path.exists():
|
| 86 |
+
_log("H2", "diag_colab_hf.py:test_h2_sft_coverage",
|
| 87 |
+
"SFT file missing", {"path": str(sft_path)})
|
| 88 |
+
return
|
| 89 |
+
new_services = ["search", "gateway", "scheduler", "storage",
|
| 90 |
+
"metrics_collector", "email", "billing", "config",
|
| 91 |
+
"dns", "loadbalancer"]
|
| 92 |
+
new_ports = [str(p) for p in range(8007, 8017)]
|
| 93 |
+
svc_hits = {svc: 0 for svc in new_services}
|
| 94 |
+
port_hits = {port: 0 for port in new_ports}
|
| 95 |
+
sample_prompt = ""
|
| 96 |
+
total = 0
|
| 97 |
+
with open(sft_path, "r", encoding="utf-8") as f:
|
| 98 |
+
for line in f:
|
| 99 |
+
total += 1
|
| 100 |
+
try:
|
| 101 |
+
obj = json.loads(line)
|
| 102 |
+
except Exception:
|
| 103 |
+
continue
|
| 104 |
+
for m in obj.get("messages", []):
|
| 105 |
+
if m.get("role") == "system":
|
| 106 |
+
txt = m.get("content", "")
|
| 107 |
+
if total == 1:
|
| 108 |
+
sample_prompt = txt[:600]
|
| 109 |
+
for svc in new_services:
|
| 110 |
+
if svc in txt:
|
| 111 |
+
svc_hits[svc] += 1
|
| 112 |
+
for port in new_ports:
|
| 113 |
+
if port in txt:
|
| 114 |
+
port_hits[port] += 1
|
| 115 |
+
break
|
| 116 |
+
if total >= 200:
|
| 117 |
+
break
|
| 118 |
+
_log("H2", "diag_colab_hf.py:test_h2_sft_coverage",
|
| 119 |
+
"SFT system-prompt coverage of new services",
|
| 120 |
+
{"sample_size_lines": total,
|
| 121 |
+
"service_mention_counts": svc_hits,
|
| 122 |
+
"port_mention_counts": port_hits,
|
| 123 |
+
"any_new_service_mentioned": any(v > 0 for v in svc_hits.values()),
|
| 124 |
+
"first_system_prompt_excerpt": sample_prompt})
|
| 125 |
+
# endregion
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# region agent log H3 β _write_service_log silent failure mode
|
| 129 |
+
def test_h3_silent_log_swallow():
|
| 130 |
+
from cloud_sre_v2.services.orchestrator import ServiceOrchestrator
|
| 131 |
+
src = inspect.getsource(ServiceOrchestrator._write_service_log)
|
| 132 |
+
has_silent_pattern = "except OSError" in src and "pass" in src
|
| 133 |
+
orch = ServiceOrchestrator()
|
| 134 |
+
tmp = Path(tempfile.mkdtemp(prefix="cloudsre_diag_"))
|
| 135 |
+
|
| 136 |
+
orch.log_dir = str(tmp)
|
| 137 |
+
orch._write_service_log("payment", "error", "DIAG_HAPPY_PATH")
|
| 138 |
+
happy_file = tmp / "payment" / "error.log"
|
| 139 |
+
happy_written = happy_file.exists() and happy_file.stat().st_size > 0
|
| 140 |
+
|
| 141 |
+
fake_parent = tmp / "log_dir_is_a_file"
|
| 142 |
+
fake_parent.write_text("not a dir")
|
| 143 |
+
orch.log_dir = str(fake_parent)
|
| 144 |
+
raised = None
|
| 145 |
+
try:
|
| 146 |
+
orch._write_service_log("payment", "error", "DIAG_BROKEN_PATH")
|
| 147 |
+
except Exception as exc:
|
| 148 |
+
raised = type(exc).__name__
|
| 149 |
+
silent_on_broken = raised is None
|
| 150 |
+
broken_file_exists = (fake_parent / "payment" / "error.log").exists()
|
| 151 |
+
_log("H3", "diag_colab_hf.py:test_h3_silent_log_swallow",
|
| 152 |
+
"_write_service_log error-handling characterization",
|
| 153 |
+
{"has_silent_except_oserror": has_silent_pattern,
|
| 154 |
+
"happy_path_wrote_file": happy_written,
|
| 155 |
+
"broken_path_silent": silent_on_broken,
|
| 156 |
+
"exception_type": raised,
|
| 157 |
+
"broken_path_file_exists": broken_file_exists,
|
| 158 |
+
"platform": sys.platform})
|
| 159 |
+
# endregion
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# region agent log H4 β fallocate availability for disk_full
|
| 163 |
+
def test_h4_fallocate_availability():
|
| 164 |
+
fpath = shutil.which("fallocate")
|
| 165 |
+
truncate_path = shutil.which("truncate")
|
| 166 |
+
df_path = shutil.which("df")
|
| 167 |
+
_log("H4", "diag_colab_hf.py:test_h4_fallocate_availability",
|
| 168 |
+
"Tool availability for OS-level disk_full injection",
|
| 169 |
+
{"platform": sys.platform,
|
| 170 |
+
"fallocate_resolved": fpath,
|
| 171 |
+
"truncate_resolved": truncate_path,
|
| 172 |
+
"df_resolved": df_path,
|
| 173 |
+
"fallocate_present": fpath is not None})
|
| 174 |
+
# endregion
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
# region agent log H5 β Scenario fault wiring coverage
|
| 178 |
+
def test_h5_fault_wiring():
|
| 179 |
+
orch_src = (WORKSPACE / "cloud_sre_v2" / "services" /
|
| 180 |
+
"orchestrator.py").read_text(encoding="utf-8")
|
| 181 |
+
consts_src = (WORKSPACE / "cloud_sre_v2" / "server" /
|
| 182 |
+
"constants.py").read_text(encoding="utf-8")
|
| 183 |
+
injector_keys = set(re.findall(
|
| 184 |
+
r'"(\w+)"\s*:\s*self\._inject_\w+', orch_src))
|
| 185 |
+
failure_types = set(re.findall(r'failure_type\s*=\s*"(\w+)"', consts_src))
|
| 186 |
+
cascade_types = set(re.findall(r'cascade_type\s*=\s*"(\w+)"', consts_src))
|
| 187 |
+
next_ftypes = set(re.findall(
|
| 188 |
+
r'next_failure_type\s*=\s*"(\w+)"', consts_src))
|
| 189 |
+
benign = {"misleading_signal", "upstream_dependency_failure"}
|
| 190 |
+
referenced = (failure_types | cascade_types | next_ftypes) - benign
|
| 191 |
+
orphans = sorted(referenced - injector_keys)
|
| 192 |
+
cascade_orphans = sorted((cascade_types | next_ftypes) - injector_keys - benign)
|
| 193 |
+
_log("H5", "diag_colab_hf.py:test_h5_fault_wiring",
|
| 194 |
+
"Scenario failure_type coverage in inject_fault dispatcher",
|
| 195 |
+
{"injector_count": len(injector_keys),
|
| 196 |
+
"failure_type_count": len(failure_types),
|
| 197 |
+
"cascade_type_count": len(cascade_types),
|
| 198 |
+
"next_failure_type_count": len(next_ftypes),
|
| 199 |
+
"orphan_failure_types": orphans,
|
| 200 |
+
"orphan_cascade_failure_types": cascade_orphans,
|
| 201 |
+
"injectors": sorted(injector_keys),
|
| 202 |
+
"failure_types_sample": sorted(failure_types)[:25]})
|
| 203 |
+
# endregion
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
# region agent log driver
|
| 207 |
+
if __name__ == "__main__":
|
| 208 |
+
_log("driver", "diag_colab_hf.py:main",
|
| 209 |
+
"Diagnostic run starting",
|
| 210 |
+
{"workspace": str(WORKSPACE),
|
| 211 |
+
"python": sys.version.split()[0],
|
| 212 |
+
"platform": sys.platform})
|
| 213 |
+
_safe("H1", "diag_colab_hf.py:test_h1_health_route", test_h1_health_route)
|
| 214 |
+
_safe("H2", "diag_colab_hf.py:test_h2_sft_coverage", test_h2_sft_coverage)
|
| 215 |
+
_safe("H3", "diag_colab_hf.py:test_h3_silent_log_swallow",
|
| 216 |
+
test_h3_silent_log_swallow)
|
| 217 |
+
_safe("H4", "diag_colab_hf.py:test_h4_fallocate_availability",
|
| 218 |
+
test_h4_fallocate_availability)
|
| 219 |
+
_safe("H5", "diag_colab_hf.py:test_h5_fault_wiring",
|
| 220 |
+
test_h5_fault_wiring)
|
| 221 |
+
_log("driver", "diag_colab_hf.py:main",
|
| 222 |
+
"Diagnostic run complete", {"log_path": str(LOG_PATH)})
|
| 223 |
+
print(f"Diagnostic complete. Log written to: {LOG_PATH}")
|
| 224 |
+
# endregion
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.9"
|
| 2 |
+
# CloudSRE v2 β Distributed Microservice Architecture
|
| 3 |
+
# 16 real containers, each with its own PID namespace, network stack, and filesystem.
|
| 4 |
+
# This is NOT localhost subprocess spawning β each service is a real Docker container.
|
| 5 |
+
#
|
| 6 |
+
# Usage:
|
| 7 |
+
# docker-compose up -d # Start all 16 services + orchestrator
|
| 8 |
+
# docker ps # See 17 running containers
|
| 9 |
+
# docker kill cloudsre-payment # Real SIGKILL β port goes dead
|
| 10 |
+
# docker pause cloudsre-dns # Real SIGSTOP β TCP timeouts cascade
|
| 11 |
+
#
|
| 12 |
+
# Architecture:
|
| 13 |
+
# 172.20.0.2 orchestrator (API server, port 7860)
|
| 14 |
+
# 172.20.0.10 payment 172.20.0.11 auth 172.20.0.12 worker
|
| 15 |
+
# 172.20.0.13 frontend 172.20.0.14 cache 172.20.0.15 notification
|
| 16 |
+
# 172.20.0.16 search 172.20.0.17 gateway 172.20.0.18 scheduler
|
| 17 |
+
# 172.20.0.19 storage 172.20.0.20 metrics 172.20.0.21 email
|
| 18 |
+
# 172.20.0.22 billing 172.20.0.23 config 172.20.0.24 dns
|
| 19 |
+
# 172.20.0.25 loadbalancer
|
| 20 |
+
|
| 21 |
+
x-service-defaults: &service-defaults
|
| 22 |
+
build:
|
| 23 |
+
context: .
|
| 24 |
+
dockerfile: Dockerfile.service
|
| 25 |
+
restart: unless-stopped
|
| 26 |
+
volumes:
|
| 27 |
+
- shared-data:/data
|
| 28 |
+
- shared-logs:/var/log
|
| 29 |
+
networks:
|
| 30 |
+
- sre-mesh
|
| 31 |
+
healthcheck:
|
| 32 |
+
test: ["CMD", "curl", "-f", "http://localhost:${PORT:-8001}/healthz"]
|
| 33 |
+
interval: 10s
|
| 34 |
+
timeout: 3s
|
| 35 |
+
retries: 3
|
| 36 |
+
|
| 37 |
+
services:
|
| 38 |
+
# ββ Orchestrator (API Gateway) βββββββββββββββββββββββββββββ
|
| 39 |
+
orchestrator:
|
| 40 |
+
build:
|
| 41 |
+
context: .
|
| 42 |
+
dockerfile: Dockerfile
|
| 43 |
+
container_name: cloudsre-orchestrator
|
| 44 |
+
ports:
|
| 45 |
+
- "7860:7860"
|
| 46 |
+
volumes:
|
| 47 |
+
- shared-data:/data
|
| 48 |
+
- shared-logs:/var/log
|
| 49 |
+
- /var/run/docker.sock:/var/run/docker.sock
|
| 50 |
+
environment:
|
| 51 |
+
- CONTAINER_MODE=docker
|
| 52 |
+
- DATA_DIR=/data
|
| 53 |
+
- LOG_DIR=/var/log
|
| 54 |
+
networks:
|
| 55 |
+
sre-mesh:
|
| 56 |
+
ipv4_address: 172.20.0.2
|
| 57 |
+
depends_on:
|
| 58 |
+
- payment
|
| 59 |
+
- auth
|
| 60 |
+
- worker
|
| 61 |
+
- frontend
|
| 62 |
+
- cache
|
| 63 |
+
- notification
|
| 64 |
+
- search
|
| 65 |
+
- gateway
|
| 66 |
+
- scheduler
|
| 67 |
+
- storage
|
| 68 |
+
- metrics_collector
|
| 69 |
+
- email
|
| 70 |
+
- billing
|
| 71 |
+
- config
|
| 72 |
+
- dns
|
| 73 |
+
- loadbalancer
|
| 74 |
+
|
| 75 |
+
# ββ us-east-1 Region βββββββββββββββββββββββββββββββββββββββ
|
| 76 |
+
payment:
|
| 77 |
+
<<: *service-defaults
|
| 78 |
+
container_name: cloudsre-payment
|
| 79 |
+
command: python -m cloud_sre_v2.services._service_worker --service payment --port 8001 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 80 |
+
ports: ["8001:8001"]
|
| 81 |
+
networks:
|
| 82 |
+
sre-mesh:
|
| 83 |
+
ipv4_address: 172.20.0.10
|
| 84 |
+
|
| 85 |
+
auth:
|
| 86 |
+
<<: *service-defaults
|
| 87 |
+
container_name: cloudsre-auth
|
| 88 |
+
command: python -m cloud_sre_v2.services._service_worker --service auth --port 8002 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 89 |
+
ports: ["8002:8002"]
|
| 90 |
+
networks:
|
| 91 |
+
sre-mesh:
|
| 92 |
+
ipv4_address: 172.20.0.11
|
| 93 |
+
|
| 94 |
+
billing:
|
| 95 |
+
<<: *service-defaults
|
| 96 |
+
container_name: cloudsre-billing
|
| 97 |
+
command: python -m cloud_sre_v2.services._service_worker --service billing --port 8013 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 98 |
+
ports: ["8013:8013"]
|
| 99 |
+
networks:
|
| 100 |
+
sre-mesh:
|
| 101 |
+
ipv4_address: 172.20.0.22
|
| 102 |
+
|
| 103 |
+
gateway:
|
| 104 |
+
<<: *service-defaults
|
| 105 |
+
container_name: cloudsre-gateway
|
| 106 |
+
command: python -m cloud_sre_v2.services._service_worker --service gateway --port 8008 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 107 |
+
ports: ["8008:8008"]
|
| 108 |
+
networks:
|
| 109 |
+
sre-mesh:
|
| 110 |
+
ipv4_address: 172.20.0.17
|
| 111 |
+
|
| 112 |
+
loadbalancer:
|
| 113 |
+
<<: *service-defaults
|
| 114 |
+
container_name: cloudsre-loadbalancer
|
| 115 |
+
command: python -m cloud_sre_v2.services._service_worker --service loadbalancer --port 8016 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 116 |
+
ports: ["8016:8016"]
|
| 117 |
+
networks:
|
| 118 |
+
sre-mesh:
|
| 119 |
+
ipv4_address: 172.20.0.25
|
| 120 |
+
|
| 121 |
+
config:
|
| 122 |
+
<<: *service-defaults
|
| 123 |
+
container_name: cloudsre-config
|
| 124 |
+
command: python -m cloud_sre_v2.services._service_worker --service config --port 8014 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 125 |
+
ports: ["8014:8014"]
|
| 126 |
+
networks:
|
| 127 |
+
sre-mesh:
|
| 128 |
+
ipv4_address: 172.20.0.23
|
| 129 |
+
|
| 130 |
+
# ββ eu-west-1 Region βββββββββββββββββββββββββββββββββββββββ
|
| 131 |
+
worker:
|
| 132 |
+
<<: *service-defaults
|
| 133 |
+
container_name: cloudsre-worker
|
| 134 |
+
command: python -m cloud_sre_v2.services._service_worker --service worker --port 8003 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 135 |
+
ports: ["8003:8003"]
|
| 136 |
+
networks:
|
| 137 |
+
sre-mesh:
|
| 138 |
+
ipv4_address: 172.20.0.12
|
| 139 |
+
|
| 140 |
+
scheduler:
|
| 141 |
+
<<: *service-defaults
|
| 142 |
+
container_name: cloudsre-scheduler
|
| 143 |
+
command: python -m cloud_sre_v2.services._service_worker --service scheduler --port 8009 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 144 |
+
ports: ["8009:8009"]
|
| 145 |
+
networks:
|
| 146 |
+
sre-mesh:
|
| 147 |
+
ipv4_address: 172.20.0.18
|
| 148 |
+
|
| 149 |
+
search:
|
| 150 |
+
<<: *service-defaults
|
| 151 |
+
container_name: cloudsre-search
|
| 152 |
+
command: python -m cloud_sre_v2.services._service_worker --service search --port 8007 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 153 |
+
ports: ["8007:8007"]
|
| 154 |
+
networks:
|
| 155 |
+
sre-mesh:
|
| 156 |
+
ipv4_address: 172.20.0.16
|
| 157 |
+
|
| 158 |
+
storage:
|
| 159 |
+
<<: *service-defaults
|
| 160 |
+
container_name: cloudsre-storage
|
| 161 |
+
command: python -m cloud_sre_v2.services._service_worker --service storage --port 8010 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 162 |
+
ports: ["8010:8010"]
|
| 163 |
+
networks:
|
| 164 |
+
sre-mesh:
|
| 165 |
+
ipv4_address: 172.20.0.19
|
| 166 |
+
|
| 167 |
+
metrics_collector:
|
| 168 |
+
<<: *service-defaults
|
| 169 |
+
container_name: cloudsre-metrics
|
| 170 |
+
command: python -m cloud_sre_v2.services._service_worker --service metrics_collector --port 8011 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 171 |
+
ports: ["8011:8011"]
|
| 172 |
+
networks:
|
| 173 |
+
sre-mesh:
|
| 174 |
+
ipv4_address: 172.20.0.20
|
| 175 |
+
|
| 176 |
+
# ββ ap-south-1 Region ββββββββββββββββββββββββββββββββββββββ
|
| 177 |
+
frontend:
|
| 178 |
+
<<: *service-defaults
|
| 179 |
+
container_name: cloudsre-frontend
|
| 180 |
+
command: python -m cloud_sre_v2.services._service_worker --service frontend --port 8004 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 181 |
+
ports: ["8004:8004"]
|
| 182 |
+
networks:
|
| 183 |
+
sre-mesh:
|
| 184 |
+
ipv4_address: 172.20.0.13
|
| 185 |
+
|
| 186 |
+
cache:
|
| 187 |
+
<<: *service-defaults
|
| 188 |
+
container_name: cloudsre-cache
|
| 189 |
+
command: python -m cloud_sre_v2.services._service_worker --service cache --port 8005 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 190 |
+
ports: ["8005:8005"]
|
| 191 |
+
networks:
|
| 192 |
+
sre-mesh:
|
| 193 |
+
ipv4_address: 172.20.0.14
|
| 194 |
+
|
| 195 |
+
notification:
|
| 196 |
+
<<: *service-defaults
|
| 197 |
+
container_name: cloudsre-notification
|
| 198 |
+
command: python -m cloud_sre_v2.services._service_worker --service notification --port 8006 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 199 |
+
ports: ["8006:8006"]
|
| 200 |
+
networks:
|
| 201 |
+
sre-mesh:
|
| 202 |
+
ipv4_address: 172.20.0.15
|
| 203 |
+
|
| 204 |
+
email:
|
| 205 |
+
<<: *service-defaults
|
| 206 |
+
container_name: cloudsre-email
|
| 207 |
+
command: python -m cloud_sre_v2.services._service_worker --service email --port 8012 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 208 |
+
ports: ["8012:8012"]
|
| 209 |
+
networks:
|
| 210 |
+
sre-mesh:
|
| 211 |
+
ipv4_address: 172.20.0.21
|
| 212 |
+
|
| 213 |
+
dns:
|
| 214 |
+
<<: *service-defaults
|
| 215 |
+
container_name: cloudsre-dns
|
| 216 |
+
command: python -m cloud_sre_v2.services._service_worker --service dns --port 8015 --db-path /data/app.db --queue-dir /data/queue --log-dir /var/log
|
| 217 |
+
ports: ["8015:8015"]
|
| 218 |
+
networks:
|
| 219 |
+
sre-mesh:
|
| 220 |
+
ipv4_address: 172.20.0.24
|
| 221 |
+
|
| 222 |
+
networks:
|
| 223 |
+
sre-mesh:
|
| 224 |
+
driver: bridge
|
| 225 |
+
ipam:
|
| 226 |
+
config:
|
| 227 |
+
- subnet: 172.20.0.0/16
|
| 228 |
+
|
| 229 |
+
volumes:
|
| 230 |
+
shared-data:
|
| 231 |
+
shared-logs:
|
services/orchestrator.py
CHANGED
|
@@ -613,43 +613,96 @@ class ServiceOrchestrator:
|
|
| 613 |
return f"Injected: {latency_ms}ms latency into {target}"
|
| 614 |
|
| 615 |
def _inject_cache_invalidation(self, target: str, params: dict) -> str:
|
| 616 |
-
"""Invalidate cache β
|
| 617 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
self._inject_fault_via_http("cache", "cache_invalidation",
|
| 619 |
"ElastiCache fully invalidated β 100% miss rate, thundering herd risk")
|
| 620 |
self._write_service_log("cache", "error",
|
| 621 |
-
"CacheService: FULL INVALIDATION β all entries evicted")
|
| 622 |
self._write_service_log("cache", "error",
|
| 623 |
"CacheService: Hit ratio dropped to 0.0% β cache is COLD")
|
| 624 |
self._write_service_log("payment", "error",
|
| 625 |
"PaymentService: cache miss rate 100% β falling through to database")
|
| 626 |
-
return "Injected: cache invalidation (cold
|
| 627 |
|
| 628 |
def _inject_webhook_storm(self, target: str, params: dict) -> str:
|
| 629 |
-
"""Trigger mass webhook retry β
|
| 630 |
-
|
| 631 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
self._inject_fault_via_http("notification", "webhook_storm",
|
| 633 |
f"Webhook storm ({count} retries) β delivery pipeline overwhelmed")
|
| 634 |
self._write_service_log("notification", "error",
|
| 635 |
-
f"NotificationService: WEBHOOK STORM β {count}
|
| 636 |
self._write_service_log("notification", "error",
|
| 637 |
f"NotificationService: Queue depth {count}/500 β delivery rate overwhelmed")
|
| 638 |
self._write_service_log("payment", "error",
|
| 639 |
"PaymentService: Inbound webhook callbacks spiking β 300 req/sec (normal: 10)")
|
| 640 |
-
return f"Injected: webhook storm ({count}
|
| 641 |
|
| 642 |
def _inject_index_corruption(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
self._inject_fault_via_http("search", "index_corruption",
|
| 644 |
"Search index corrupted β queries returning empty or incorrect results")
|
| 645 |
-
self._write_service_log("search", "error",
|
| 646 |
-
|
|
|
|
| 647 |
|
| 648 |
def _inject_index_lag(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
self._inject_fault_via_http("search", "index_lag",
|
| 650 |
"Search index lagging by >1000 docs β stale query results")
|
| 651 |
-
self._write_service_log("search", "error",
|
| 652 |
-
|
|
|
|
| 653 |
|
| 654 |
def _inject_rate_limit_zero(self, target: str, params: dict) -> str:
|
| 655 |
"""Rate limit zero β SIGSTOP the gateway process briefly to cause real connection drops.
|
|
@@ -772,16 +825,35 @@ class ServiceOrchestrator:
|
|
| 772 |
return "Injected: storage data corruption (physical DB corruption + HTTP 503)"
|
| 773 |
|
| 774 |
def _inject_scrape_failure(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
self._inject_fault_via_http("metrics_collector", "scrape_failure",
|
| 776 |
"Metrics collector scrape failures β telemetry stale, alerting blind spots")
|
| 777 |
self._write_service_log("metrics_collector", "error", "MetricsCollector: scrape failures on all targets")
|
| 778 |
-
return "Injected: metrics scrape failure"
|
| 779 |
|
| 780 |
def _inject_retention_full(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
self._inject_fault_via_http("metrics_collector", "retention_full",
|
| 782 |
"Metrics retention full β dropping new datapoints", severity="critical")
|
| 783 |
-
self._write_service_log("metrics_collector", "error",
|
| 784 |
-
|
|
|
|
| 785 |
|
| 786 |
def _inject_smtp_down(self, target: str, params: dict) -> str:
|
| 787 |
"""SMTP down β SIGSTOP the email process to physically stop delivery.
|
|
@@ -804,28 +876,72 @@ class ServiceOrchestrator:
|
|
| 804 |
return "Injected: email SMTP down (SIGSTOP)"
|
| 805 |
|
| 806 |
def _inject_email_queue_overflow(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 807 |
self._inject_fault_via_http("email", "email_queue_overflow",
|
| 808 |
"Email queue overflow β messages dropped")
|
| 809 |
-
self._write_service_log("email", "error",
|
| 810 |
-
|
|
|
|
| 811 |
|
| 812 |
def _inject_billing_desync(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 813 |
self._inject_fault_via_http("billing", "billing_desync",
|
| 814 |
"Billing desync β charges recorded but not reconciled")
|
| 815 |
-
self._write_service_log("billing", "error",
|
| 816 |
-
|
|
|
|
| 817 |
|
| 818 |
def _inject_invoice_stuck(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
self._inject_fault_via_http("billing", "invoice_stuck",
|
| 820 |
"Invoice generation stuck β invoices not being produced")
|
| 821 |
self._write_service_log("billing", "error", "BillingService: invoice generation scheduler stuck")
|
| 822 |
-
return "Injected: invoice generation stuck"
|
| 823 |
|
| 824 |
def _inject_config_poisoned(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 825 |
self._inject_fault_via_http("config", "config_poisoned",
|
| 826 |
"Config store poisoned β critical keys set to unsafe values")
|
| 827 |
-
self._write_service_log("config", "error",
|
| 828 |
-
|
|
|
|
| 829 |
|
| 830 |
def _inject_config_locked(self, target: str, params: dict) -> str:
|
| 831 |
"""Config locked β SIGSTOP the config process to physically block reads.
|
|
@@ -869,22 +985,54 @@ class ServiceOrchestrator:
|
|
| 869 |
return "Injected: DNS resolution failure (SIGSTOP)"
|
| 870 |
|
| 871 |
def _inject_stale_entries(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
self._inject_fault_via_http("dns", "stale_entries",
|
| 873 |
"DNS stale entries β services routed to dead endpoints")
|
| 874 |
-
self._write_service_log("dns", "error",
|
| 875 |
-
|
|
|
|
| 876 |
|
| 877 |
def _inject_all_backends_removed(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
self._inject_fault_via_http("loadbalancer", "all_backends_removed",
|
| 879 |
"Load balancer has no healthy backends β traffic returning 503", severity="critical")
|
| 880 |
self._write_service_log("loadbalancer", "error", "LoadBalancer: all backends removed from active pool")
|
| 881 |
-
return "Injected: all backends removed"
|
| 882 |
|
| 883 |
def _inject_session_corruption(self, target: str, params: dict) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
self._inject_fault_via_http("loadbalancer", "session_corruption",
|
| 885 |
"Sticky-session corruption β users routed inconsistently")
|
| 886 |
-
self._write_service_log("loadbalancer", "error",
|
| 887 |
-
|
|
|
|
| 888 |
|
| 889 |
# ββ Log Writing ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 890 |
|
|
|
|
| 613 |
return f"Injected: {latency_ms}ms latency into {target}"
|
| 614 |
|
| 615 |
def _inject_cache_invalidation(self, target: str, params: dict) -> str:
|
| 616 |
+
"""Invalidate cache β physically delete cache data files.
|
| 617 |
+
|
| 618 |
+
OS-LEVEL: Deletes all files in the cache data directory, then
|
| 619 |
+
SIGSTOP the cache process for 2s to simulate a cold restart.
|
| 620 |
+
"""
|
| 621 |
+
# Physical: delete cache data files
|
| 622 |
+
cache_dir = os.path.join(self.data_dir, "cache")
|
| 623 |
+
os.makedirs(cache_dir, exist_ok=True)
|
| 624 |
+
for fname in os.listdir(cache_dir):
|
| 625 |
+
try:
|
| 626 |
+
os.remove(os.path.join(cache_dir, fname))
|
| 627 |
+
except OSError:
|
| 628 |
+
pass
|
| 629 |
+
# Physical: SIGSTOP cache process briefly to simulate cold restart
|
| 630 |
+
pid = self._processes.get("cache", {}).get("pid")
|
| 631 |
+
if pid and sys.platform != "win32":
|
| 632 |
+
try:
|
| 633 |
+
os.kill(pid, signal.SIGSTOP)
|
| 634 |
+
threading.Timer(2.0, lambda: os.kill(pid, signal.SIGCONT)).start()
|
| 635 |
+
except ProcessLookupError:
|
| 636 |
+
pass
|
| 637 |
self._inject_fault_via_http("cache", "cache_invalidation",
|
| 638 |
"ElastiCache fully invalidated β 100% miss rate, thundering herd risk")
|
| 639 |
self._write_service_log("cache", "error",
|
| 640 |
+
"CacheService: FULL INVALIDATION β all entries evicted (files deleted)")
|
| 641 |
self._write_service_log("cache", "error",
|
| 642 |
"CacheService: Hit ratio dropped to 0.0% β cache is COLD")
|
| 643 |
self._write_service_log("payment", "error",
|
| 644 |
"PaymentService: cache miss rate 100% β falling through to database")
|
| 645 |
+
return "Injected: cache invalidation (files deleted + SIGSTOP cold restart)"
|
| 646 |
|
| 647 |
def _inject_webhook_storm(self, target: str, params: dict) -> str:
|
| 648 |
+
"""Trigger mass webhook retry β sends REAL HTTP requests to overwhelm the service.
|
| 649 |
+
|
| 650 |
+
OS-LEVEL: Spawns threads that fire actual HTTP requests at the
|
| 651 |
+
notification service, creating real connection exhaustion.
|
| 652 |
+
"""
|
| 653 |
+
count = params.get("count", 100)
|
| 654 |
+
port = self._processes.get("notification", {}).get("port", 8006)
|
| 655 |
+
# Physical: fire real HTTP requests in background threads
|
| 656 |
+
def _storm():
|
| 657 |
+
import httpx as _httpx
|
| 658 |
+
for _ in range(count):
|
| 659 |
+
try:
|
| 660 |
+
_httpx.post(f"http://localhost:{port}/webhook",
|
| 661 |
+
json={"event": "payment.retry", "attempt": _},
|
| 662 |
+
timeout=1.0)
|
| 663 |
+
except Exception:
|
| 664 |
+
pass
|
| 665 |
+
storm_thread = threading.Thread(target=_storm, daemon=True)
|
| 666 |
+
storm_thread.start()
|
| 667 |
self._inject_fault_via_http("notification", "webhook_storm",
|
| 668 |
f"Webhook storm ({count} retries) β delivery pipeline overwhelmed")
|
| 669 |
self._write_service_log("notification", "error",
|
| 670 |
+
f"NotificationService: WEBHOOK STORM β {count} real HTTP requests fired")
|
| 671 |
self._write_service_log("notification", "error",
|
| 672 |
f"NotificationService: Queue depth {count}/500 β delivery rate overwhelmed")
|
| 673 |
self._write_service_log("payment", "error",
|
| 674 |
"PaymentService: Inbound webhook callbacks spiking β 300 req/sec (normal: 10)")
|
| 675 |
+
return f"Injected: webhook storm ({count} real HTTP requests)"
|
| 676 |
|
| 677 |
def _inject_index_corruption(self, target: str, params: dict) -> str:
|
| 678 |
+
"""Search index corruption β write corrupt data to index file on disk."""
|
| 679 |
+
index_file = os.path.join(self.data_dir, "search_index.dat")
|
| 680 |
+
try:
|
| 681 |
+
with open(index_file, "wb") as f:
|
| 682 |
+
f.write(os.urandom(1024)) # Physical corruption
|
| 683 |
+
except OSError:
|
| 684 |
+
pass
|
| 685 |
self._inject_fault_via_http("search", "index_corruption",
|
| 686 |
"Search index corrupted β queries returning empty or incorrect results")
|
| 687 |
+
self._write_service_log("search", "error",
|
| 688 |
+
f"SearchService: index corruption detected β {index_file} has invalid checksum")
|
| 689 |
+
return "Injected: search index corruption (physical file corruption)"
|
| 690 |
|
| 691 |
def _inject_index_lag(self, target: str, params: dict) -> str:
|
| 692 |
+
"""Search index lag β write 1000+ pending docs to a backlog file."""
|
| 693 |
+
backlog_file = os.path.join(self.data_dir, "search_backlog.json")
|
| 694 |
+
try:
|
| 695 |
+
import json as _json
|
| 696 |
+
backlog = [{"doc_id": i, "status": "pending"} for i in range(1200)]
|
| 697 |
+
with open(backlog_file, "w") as f:
|
| 698 |
+
_json.dump(backlog, f)
|
| 699 |
+
except OSError:
|
| 700 |
+
pass
|
| 701 |
self._inject_fault_via_http("search", "index_lag",
|
| 702 |
"Search index lagging by >1000 docs β stale query results")
|
| 703 |
+
self._write_service_log("search", "error",
|
| 704 |
+
f"SearchService: index lag critical β {backlog_file} has 1200 pending docs")
|
| 705 |
+
return "Injected: search index lag (1200 docs in physical backlog)"
|
| 706 |
|
| 707 |
def _inject_rate_limit_zero(self, target: str, params: dict) -> str:
|
| 708 |
"""Rate limit zero β SIGSTOP the gateway process briefly to cause real connection drops.
|
|
|
|
| 825 |
return "Injected: storage data corruption (physical DB corruption + HTTP 503)"
|
| 826 |
|
| 827 |
def _inject_scrape_failure(self, target: str, params: dict) -> str:
|
| 828 |
+
"""Metrics scrape failure β SIGSTOP the metrics_collector process."""
|
| 829 |
+
pid = self._processes.get("metrics_collector", {}).get("pid")
|
| 830 |
+
if pid and sys.platform != "win32":
|
| 831 |
+
try:
|
| 832 |
+
os.kill(pid, signal.SIGSTOP)
|
| 833 |
+
self._write_service_log("metrics_collector", "error",
|
| 834 |
+
"MetricsCollector: process frozen (SIGSTOP) β scrapes halted")
|
| 835 |
+
except ProcessLookupError:
|
| 836 |
+
pass
|
| 837 |
self._inject_fault_via_http("metrics_collector", "scrape_failure",
|
| 838 |
"Metrics collector scrape failures β telemetry stale, alerting blind spots")
|
| 839 |
self._write_service_log("metrics_collector", "error", "MetricsCollector: scrape failures on all targets")
|
| 840 |
+
return "Injected: metrics scrape failure (SIGSTOP)"
|
| 841 |
|
| 842 |
def _inject_retention_full(self, target: str, params: dict) -> str:
|
| 843 |
+
"""Metrics retention full β fill the metrics data directory with junk."""
|
| 844 |
+
metrics_dir = os.path.join(self.data_dir, "metrics")
|
| 845 |
+
os.makedirs(metrics_dir, exist_ok=True)
|
| 846 |
+
try:
|
| 847 |
+
junk_path = os.path.join(metrics_dir, "_retention_full.bin")
|
| 848 |
+
with open(junk_path, "wb") as f:
|
| 849 |
+
f.write(b"\x00" * (5 * 1024 * 1024)) # 5MB junk file
|
| 850 |
+
except OSError:
|
| 851 |
+
pass
|
| 852 |
self._inject_fault_via_http("metrics_collector", "retention_full",
|
| 853 |
"Metrics retention full β dropping new datapoints", severity="critical")
|
| 854 |
+
self._write_service_log("metrics_collector", "error",
|
| 855 |
+
"MetricsCollector: retention store full (5MB junk file), dropping ingestion")
|
| 856 |
+
return "Injected: metrics retention full (physical disk fill)"
|
| 857 |
|
| 858 |
def _inject_smtp_down(self, target: str, params: dict) -> str:
|
| 859 |
"""SMTP down β SIGSTOP the email process to physically stop delivery.
|
|
|
|
| 876 |
return "Injected: email SMTP down (SIGSTOP)"
|
| 877 |
|
| 878 |
def _inject_email_queue_overflow(self, target: str, params: dict) -> str:
|
| 879 |
+
"""Email queue overflow β write real backlog file to disk."""
|
| 880 |
+
queue_file = os.path.join(self.data_dir, "email_queue.json")
|
| 881 |
+
try:
|
| 882 |
+
import json as _json
|
| 883 |
+
backlog = [{"to": f"user{i}@example.com", "status": "queued"} for i in range(500)]
|
| 884 |
+
with open(queue_file, "w") as f:
|
| 885 |
+
_json.dump(backlog, f)
|
| 886 |
+
except OSError:
|
| 887 |
+
pass
|
| 888 |
self._inject_fault_via_http("email", "email_queue_overflow",
|
| 889 |
"Email queue overflow β messages dropped")
|
| 890 |
+
self._write_service_log("email", "error",
|
| 891 |
+
f"EmailService: queue overflow β 500 messages in {queue_file}")
|
| 892 |
+
return "Injected: email queue overflow (500 msgs in physical backlog)"
|
| 893 |
|
| 894 |
def _inject_billing_desync(self, target: str, params: dict) -> str:
|
| 895 |
+
"""Billing desync β insert unreconciled charges into the real database."""
|
| 896 |
+
try:
|
| 897 |
+
for i in range(10):
|
| 898 |
+
self.database.execute(
|
| 899 |
+
"INSERT INTO payments (amount, user_id, status, created_at) "
|
| 900 |
+
"VALUES (?, ?, 'desync_unreconciled', datetime('now'))",
|
| 901 |
+
(round(99.99 + i, 2), f"billing_ghost_{i}"),
|
| 902 |
+
)
|
| 903 |
+
except Exception:
|
| 904 |
+
pass
|
| 905 |
self._inject_fault_via_http("billing", "billing_desync",
|
| 906 |
"Billing desync β charges recorded but not reconciled")
|
| 907 |
+
self._write_service_log("billing", "error",
|
| 908 |
+
"BillingService: ledger desync β 10 unreconciled charges in DB")
|
| 909 |
+
return "Injected: billing desync (10 ghost charges in DB)"
|
| 910 |
|
| 911 |
def _inject_invoice_stuck(self, target: str, params: dict) -> str:
|
| 912 |
+
"""Invoice stuck β SIGSTOP the billing process."""
|
| 913 |
+
pid = self._processes.get("billing", {}).get("pid")
|
| 914 |
+
if pid and sys.platform != "win32":
|
| 915 |
+
try:
|
| 916 |
+
os.kill(pid, signal.SIGSTOP)
|
| 917 |
+
self._write_service_log("billing", "error",
|
| 918 |
+
"BillingService: process frozen (SIGSTOP) β invoices stuck")
|
| 919 |
+
except ProcessLookupError:
|
| 920 |
+
pass
|
| 921 |
self._inject_fault_via_http("billing", "invoice_stuck",
|
| 922 |
"Invoice generation stuck β invoices not being produced")
|
| 923 |
self._write_service_log("billing", "error", "BillingService: invoice generation scheduler stuck")
|
| 924 |
+
return "Injected: invoice generation stuck (SIGSTOP)"
|
| 925 |
|
| 926 |
def _inject_config_poisoned(self, target: str, params: dict) -> str:
|
| 927 |
+
"""Config poisoned β write dangerous values to real config file."""
|
| 928 |
+
config_file = os.path.join(self.data_dir, "config.json")
|
| 929 |
+
try:
|
| 930 |
+
import json as _json
|
| 931 |
+
poisoned = {
|
| 932 |
+
"rate_limit": 0, "queue_max": 5, "timeout_ms": 1,
|
| 933 |
+
"debug_mode": True, "auth_bypass": True,
|
| 934 |
+
"_poisoned": True, "_timestamp": time.time(),
|
| 935 |
+
}
|
| 936 |
+
with open(config_file, "w") as f:
|
| 937 |
+
_json.dump(poisoned, f)
|
| 938 |
+
except OSError:
|
| 939 |
+
pass
|
| 940 |
self._inject_fault_via_http("config", "config_poisoned",
|
| 941 |
"Config store poisoned β critical keys set to unsafe values")
|
| 942 |
+
self._write_service_log("config", "error",
|
| 943 |
+
f"ConfigService: poisoned config written to {config_file} (rate_limit=0, auth_bypass=True)")
|
| 944 |
+
return "Injected: config poisoned (physical config file corrupted)"
|
| 945 |
|
| 946 |
def _inject_config_locked(self, target: str, params: dict) -> str:
|
| 947 |
"""Config locked β SIGSTOP the config process to physically block reads.
|
|
|
|
| 985 |
return "Injected: DNS resolution failure (SIGSTOP)"
|
| 986 |
|
| 987 |
def _inject_stale_entries(self, target: str, params: dict) -> str:
|
| 988 |
+
"""DNS stale entries β write stale host mappings to a real DNS cache file."""
|
| 989 |
+
dns_cache = os.path.join(self.data_dir, "dns_cache.json")
|
| 990 |
+
try:
|
| 991 |
+
import json as _json
|
| 992 |
+
stale = {
|
| 993 |
+
"payment": "10.0.0.99", # dead IP
|
| 994 |
+
"auth": "10.0.0.98",
|
| 995 |
+
"gateway": "192.168.0.1", # wrong subnet
|
| 996 |
+
"_ttl": 0, "_stale": True,
|
| 997 |
+
}
|
| 998 |
+
with open(dns_cache, "w") as f:
|
| 999 |
+
_json.dump(stale, f)
|
| 1000 |
+
except OSError:
|
| 1001 |
+
pass
|
| 1002 |
self._inject_fault_via_http("dns", "stale_entries",
|
| 1003 |
"DNS stale entries β services routed to dead endpoints")
|
| 1004 |
+
self._write_service_log("dns", "error",
|
| 1005 |
+
f"DNSService: stale cache at {dns_cache} β serving dead IPs")
|
| 1006 |
+
return "Injected: DNS stale entries (physical cache file poisoned)"
|
| 1007 |
|
| 1008 |
def _inject_all_backends_removed(self, target: str, params: dict) -> str:
|
| 1009 |
+
"""All backends removed β SIGSTOP the loadbalancer to physically drop traffic."""
|
| 1010 |
+
pid = self._processes.get("loadbalancer", {}).get("pid")
|
| 1011 |
+
if pid and sys.platform != "win32":
|
| 1012 |
+
try:
|
| 1013 |
+
os.kill(pid, signal.SIGSTOP)
|
| 1014 |
+
self._write_service_log("loadbalancer", "error",
|
| 1015 |
+
"LoadBalancer: process frozen (SIGSTOP) β all backends offline")
|
| 1016 |
+
except ProcessLookupError:
|
| 1017 |
+
pass
|
| 1018 |
self._inject_fault_via_http("loadbalancer", "all_backends_removed",
|
| 1019 |
"Load balancer has no healthy backends β traffic returning 503", severity="critical")
|
| 1020 |
self._write_service_log("loadbalancer", "error", "LoadBalancer: all backends removed from active pool")
|
| 1021 |
+
return "Injected: all backends removed (SIGSTOP)"
|
| 1022 |
|
| 1023 |
def _inject_session_corruption(self, target: str, params: dict) -> str:
|
| 1024 |
+
"""Session corruption β physically corrupt session rows in SQLite."""
|
| 1025 |
+
try:
|
| 1026 |
+
self.database.execute(
|
| 1027 |
+
"UPDATE sessions SET token = 'CORRUPTED_' || token, is_valid = 0"
|
| 1028 |
+
)
|
| 1029 |
+
except Exception:
|
| 1030 |
+
pass
|
| 1031 |
self._inject_fault_via_http("loadbalancer", "session_corruption",
|
| 1032 |
"Sticky-session corruption β users routed inconsistently")
|
| 1033 |
+
self._write_service_log("loadbalancer", "error",
|
| 1034 |
+
"LoadBalancer: sticky session table corrupted (DB rows modified)")
|
| 1035 |
+
return "Injected: session corruption (physical DB row corruption)"
|
| 1036 |
|
| 1037 |
# ββ Log Writing ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1038 |
|