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

Files changed (4) hide show
  1. Dockerfile.service +34 -0
  2. diag_colab_hf.py +224 -0
  3. docker-compose.yml +231 -0
  4. 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 β€” triggers cold cache and potential thundering herd."""
617
- # Mark cache as DEGRADED β€” cold cache = 100% miss rate = service degraded
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 cache)"
627
 
628
  def _inject_webhook_storm(self, target: str, params: dict) -> str:
629
- """Trigger mass webhook retry β€” simulates Stripe-style retry storm."""
630
- count = params.get("count", 300)
631
- # Mark notification as DEGRADED β€” webhook storm overwhelms delivery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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} webhooks queued for retry")
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} retries)"
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", "SearchService: index corruption detected, checksum mismatch")
646
- return "Injected: search index corruption"
 
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", "SearchService: index lag critical β€” backlog >1000 documents")
652
- return "Injected: search index lag"
 
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", "MetricsCollector: retention store full, dropping ingestion")
784
- return "Injected: metrics retention full"
 
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", "EmailService: queue overflow threshold exceeded, dropping messages")
810
- return "Injected: email queue overflow"
 
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", "BillingService: ledger desync, pending charges not applied")
816
- return "Injected: billing desync"
 
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", "ConfigService: poisoned config values detected (rate_limit=0, queue_max=5)")
828
- return "Injected: config poisoned"
 
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", "DNSService: stale cache entries serving invalid hosts")
875
- return "Injected: DNS stale entries"
 
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", "LoadBalancer: sticky session table corrupted")
887
- return "Injected: load balancer session corruption"
 
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