Coverage for scripts / live_release_validation / actions / topology.py: 100.00%
93 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
1"""topology: verify stacks, EKS, HTTPS ALB targets, APIs, queues, and DynamoDB."""
3from __future__ import annotations
5import json
6from typing import Any
8from ..checks.alb_tls import _alb_https_target_evidence
9from ..checks.topology import (
10 _bounded_topology_evidence,
11 _converge_region_addons,
12 _health_stability_samples,
13 _health_warmup_samples,
14 _metrics_reachability_samples,
15 _queue_counts,
16)
17from ..constants import (
18 _HEALTHY_STACK_STATUSES,
19)
20from ..context import (
21 _direct_regional_access_enabled,
22)
23from ..inventory import (
24 describe_stack,
25)
26from ..models import RunContext, utc_now
27from ..ownership.stacks import (
28 _reconcile_stack_ownership,
29 _record_stack_identity,
30)
33def action_topology(ctx: RunContext) -> dict[str, Any]:
34 """Verify deterministic add-on convergence before stable API and data-plane health."""
35 _reconcile_stack_ownership(ctx)
36 stack_details: dict[str, Any] = {}
37 target_stack_regions = ctx.checkpoint.state["target_stack_regions"]
38 for stack_name, region in target_stack_regions.items():
39 stack = describe_stack(ctx.session, str(region), stack_name)
40 if stack is None:
41 raise RuntimeError(f"Expected deployed stack is absent: {stack_name} ({region})")
42 _record_stack_identity(ctx, stack_name, str(region), stack)
43 if stack["status"] not in _HEALTHY_STACK_STATUSES:
44 raise RuntimeError(
45 f"Stack {stack_name} is {stack['status']}, expected one of "
46 f"{sorted(_HEALTHY_STACK_STATUSES)}"
47 )
48 stack_details[stack_name] = stack
50 convergence: dict[str, Any] = {
51 "started_at": utc_now(),
52 "status": "running",
53 "regions": {},
54 }
55 ctx.checkpoint.state["topology_convergence"] = convergence
56 ctx.persist()
57 for region in ctx.deployment_regions:
58 stack_name = f"{ctx.config.project_name}-{region}"
59 evidence: dict[str, Any] = {
60 "region": region,
61 "stack_name": stack_name,
62 "result": "pending",
63 "observations": [],
64 }
65 convergence["regions"][region] = evidence
66 try:
67 if target_stack_regions.get(stack_name) != region:
68 raise RuntimeError(
69 f"Checkpoint does not bind exact regional stack {stack_name} to {region}"
70 )
71 stack = stack_details.get(stack_name)
72 if not isinstance(stack, dict):
73 raise RuntimeError(f"Exact regional stack was not described: {region}:{stack_name}")
74 _converge_region_addons(
75 ctx,
76 region=region,
77 stack_name=stack_name,
78 stack=stack,
79 evidence=evidence,
80 )
81 except Exception as exc:
82 evidence["result"] = "failed"
83 evidence["completed_at"] = utc_now()
84 evidence["error"] = _bounded_topology_evidence(f"{type(exc).__name__}: {exc}")
85 convergence["status"] = "failed"
86 convergence["completed_at"] = utc_now()
87 ctx.persist()
88 raise
89 evidence["result"] = "succeeded"
90 evidence["completed_at"] = utc_now()
91 ctx.persist()
92 convergence["status"] = "succeeded"
93 convergence["completed_at"] = utc_now()
94 ctx.persist()
96 clusters: dict[str, Any] = {}
97 for region in ctx.deployment_regions:
98 name = f"{ctx.config.project_name}-{region}"
99 cluster = ctx.session.client("eks", region_name=region).describe_cluster(name=name)[
100 "cluster"
101 ]
102 if cluster.get("status") != "ACTIVE":
103 raise RuntimeError(f"EKS cluster {name} is not ACTIVE: {cluster.get('status')}")
104 clusters[region] = {
105 "name": name,
106 "arn": cluster.get("arn"),
107 "status": cluster.get("status"),
108 "version": cluster.get("version"),
109 "endpoint_public_access": (cluster.get("resourcesVpcConfig") or {}).get(
110 "endpointPublicAccess"
111 ),
112 "endpoint_private_access": (cluster.get("resourcesVpcConfig") or {}).get(
113 "endpointPrivateAccess"
114 ),
115 }
117 alb_https_targets = {
118 region: _alb_https_target_evidence(
119 ctx,
120 region=region,
121 cluster_name=f"{ctx.config.project_name}-{region}",
122 )
123 for region in ctx.deployment_regions
124 }
126 global_endpoint = ctx.aws_client.get_api_endpoint(force_refresh=True)
127 global_url = str(getattr(global_endpoint, "url", "") or "")
128 if not global_url:
129 raise RuntimeError("Global API endpoint has no URL")
130 direct_regional_access = _direct_regional_access_enabled(ctx)
131 regional_urls: dict[str, str] = {}
132 if direct_regional_access:
133 for region in ctx.deployment_regions:
134 endpoint = ctx.aws_client.get_regional_api_endpoint(region, force_refresh=True)
135 endpoint_url = str(getattr(endpoint, "url", "") or "")
136 if not endpoint_url:
137 raise RuntimeError(f"Direct regional API endpoint is absent in {region}")
138 regional_urls[region] = endpoint_url
140 health_warmup_samples = _health_warmup_samples(
141 ctx,
142 global_url=global_url,
143 regional_urls=regional_urls,
144 )
145 health_samples = _health_stability_samples(
146 ctx,
147 global_url=global_url,
148 regional_urls=regional_urls,
149 )
150 metrics_samples = _metrics_reachability_samples(
151 ctx,
152 global_url=global_url,
153 regional_urls=regional_urls,
154 )
155 global_samples = [sample for sample in health_samples if sample["scope"] == "global"]
156 global_api = {
157 "url": global_url,
158 "health": global_samples[-1]["payload"],
159 "samples": global_samples,
160 }
161 if direct_regional_access:
162 regional_endpoints = {
163 region: {
164 "url": regional_urls[region],
165 "health": next(
166 sample["payload"]
167 for sample in reversed(health_samples)
168 if sample["region"] == region
169 ),
170 "samples": [sample for sample in health_samples if sample["region"] == region],
171 }
172 for region in ctx.deployment_regions
173 }
174 else:
175 regional_endpoints = {
176 region: {
177 "skipped": True,
178 "reason": "direct caller access is disabled by cdk.json",
179 "samples": [],
180 }
181 for region in ctx.deployment_regions
182 }
184 queue_baseline: dict[str, Any] = {}
185 for region in ctx.deployment_regions:
186 status = ctx.job_manager.get_queue_status(region)
187 counts = _queue_counts(status)
188 if any(counts.values()):
189 raise RuntimeError(
190 f"Fresh queue in {region} is not empty: {json.dumps(counts, sort_keys=True)}"
191 )
192 queue_baseline[region] = status
194 table_name = f"{ctx.config.project_name}-jobs"
195 table = ctx.session.client("dynamodb", region_name=ctx.config.global_region).describe_table(
196 TableName=table_name
197 )["Table"]
198 if table.get("TableStatus") != "ACTIVE":
199 raise RuntimeError(f"DynamoDB table {table_name} is not ACTIVE")
201 ctx.checkpoint.state["queue_baseline"] = queue_baseline
202 ctx.persist()
203 return {
204 "stacks": stack_details,
205 "clusters": clusters,
206 "convergence": convergence,
207 "alb_https_targets": alb_https_targets,
208 "health_warmup_samples": health_warmup_samples,
209 "health_samples": health_samples,
210 "metrics_samples": metrics_samples,
211 "global_api": global_api,
212 "regional_apis": regional_endpoints,
213 "queue_baseline": queue_baseline,
214 "jobs_table": {
215 "name": table_name,
216 "arn": table.get("TableArn"),
217 "status": table.get("TableStatus"),
218 },
219 }