Coverage for gco / services / inference_monitor.py: 100.00%

2004 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-09-14 22:07 +0000

1""" 

2Inference Monitor — reconciliation controller for inference endpoints. 

3 

4Runs in each regional EKS cluster and polls the global DynamoDB table 

5(gco-inference-endpoints) to reconcile desired state with actual 

6Kubernetes resources. Follows a GitOps-style reconciliation pattern: 

7 

8 DynamoDB (desired state) → inference_monitor → Kubernetes (actual state) 

9 

10The monitor: 

11- Creates and reconciles Deployments, ClusterIP Services, and optional autoscalers 

12- Leaves public routing on the shared ``gco-system/gco-gateway`` HTTPRoute: 

13 ``/inference`` -> ``gco-system/inference-proxy`` 

14- Removes legacy endpoint-specific Ingresses so upgrades cannot retain a bypass 

15- Updates existing deployments when spec changes 

16- Scales deployments up/down 

17- Tears down resources when endpoints are deleted 

18- Reports per-region status back to DynamoDB 

19 

20Environment Variables: 

21 CLUSTER_NAME: Name of the EKS cluster 

22 REGION: AWS region this monitor runs in 

23 INFERENCE_ENDPOINTS_TABLE_NAME: DynamoDB table name 

24 RECONCILE_INTERVAL_SECONDS: Seconds between reconciliation loops (default: 15) 

25 INFERENCE_NAMESPACE: Namespace for inference workloads (default: gco-inference) 

26""" 

27 

28import asyncio 

29import base64 

30import contextlib 

31import json 

32import logging 

33import os 

34import re 

35import secrets 

36import signal 

37import threading 

38import time 

39from collections.abc import Callable, Iterator 

40from dataclasses import dataclass, field 

41from datetime import UTC, datetime 

42from functools import partial 

43from pathlib import Path 

44from typing import Any, NoReturn, TypedDict 

45from urllib.parse import urlsplit 

46 

47from kubernetes import client, config 

48from kubernetes.client.models import V1Deployment 

49from kubernetes.client.rest import ApiException 

50 

51from gco.services.inference_store import InferenceEndpointStore 

52from gco.services.structured_logging import configure_structured_logging 

53 

54# <pyflowchart-code-diagram> BEGIN - auto-inserted, do not edit 

55# Generated at (UTC): 2026-09-13T13:44:22Z 

56# Generated from Git commit: c49331669c66625fecfecf44ae6ab5f95afbfcb4 

57# Flowchart(s) generated from this file: 

58# * ``InferenceMonitor._reconcile_endpoint_authorized`` -> ``diagrams/code_diagrams/gco/services/inference_monitor.InferenceMonitor__reconcile_endpoint_authorized.html`` 

59# (PNG: ``diagrams/code_diagrams/gco/services/inference_monitor.InferenceMonitor__reconcile_endpoint_authorized.png``) 

60# Regenerate with ``SOURCE_DATE_EPOCH=<unix-seconds> GCO_DIAGRAM_SOURCE_COMMIT=<40-char-sha> python diagrams/generate.py --code-only``. 

61# <pyflowchart-code-diagram> END 

62 

63 

64logging.basicConfig( 

65 level=logging.INFO, 

66 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 

67) 

68logger = logging.getLogger(__name__) 

69 

70 

71class NetworkPolicyApplyError(Exception): 

72 """An intra-namespace allow rule could not be applied. 

73 

74 Raised when materialization-time enforcement fails to create or verify one 

75 of the allow rules that disaggregated inference depends on. The default-deny 

76 posture is left intact — only the widening allow rule failed — and ``rule`` 

77 names the offending NetworkPolicy so callers can surface exactly which rule 

78 could not be applied. 

79 """ 

80 

81 def __init__(self, rule: str, reason: str): 

82 self.rule = rule 

83 self.reason = reason 

84 super().__init__(f"Network policy {rule!r} could not be applied: {reason}") 

85 

86 

87class AdminApiKeySecretError(Exception): 

88 """A user-named proxy admin API key Secret is missing or empty. 

89 

90 Raised before the prefill-decode proxy is materialized when a Secret named 

91 by ``proxy.admin_api_key_secret`` is absent or carries no usable 

92 ``ADMIN_API_KEY`` value. An endpoint that names no Secret takes the separate 

93 auto-managed path and receives a generated ``{name}-admin`` Secret. The 

94 proxy never starts without a usable key, so no proxy Deployment or Service 

95 is created on this error. ``secret`` records the rejected Secret name. 

96 """ 

97 

98 def __init__(self, secret: str | None, reason: str): 

99 self.secret = secret 

100 self.reason = reason 

101 named = repr(secret) if secret else "<unnamed>" 

102 super().__init__(f"Admin API key Secret {named} is unusable: {reason}") 

103 

104 

105# Official AWS CLI v2 multi-architecture image. Keep the readable release tag 

106# and immutable manifest-list digest together: both amd64 and arm64 inference 

107# nodes resolve through this single verified index. 

108AWS_CLI_IMAGE = ( 

109 "public.ecr.aws/aws-cli/aws-cli:2.36.44@" 

110 "sha256:e8467f2c319f9bc9a1471808a69949a76915e9c95eaf4a09ece9f9e85fd32747" 

111) 

112 

113# Valid TCP port boundaries for KV-transfer bootstrap ports. 

114MIN_BOOTSTRAP_PORT = 1024 

115MAX_BOOTSTRAP_PORT = 65535 

116 

117# vLLM kv_role for each worker role: prefill produces KV, decode consumes it, 

118# and a single-instance store node both produces and consumes. 

119_KV_ROLE_BY_WORKER_ROLE = { 

120 "prefill": "kv_producer", 

121 "decode": "kv_consumer", 

122 "single": "kv_both", 

123} 

124 

125# The worker roles each mooncake mode supports. Disaggregated and both split 

126# work across prefill/decode; store runs a single kv_both instance. 

127_WORKER_ROLES_BY_MODE = { 

128 "disaggregated": {"prefill", "decode"}, 

129 "store": {"single"}, 

130 "both": {"prefill", "decode"}, 

131} 

132 

133# The EFA RDMA fabric is advertised as a Kubernetes extended resource, gated by 

134# a node taint, and selected through a node label. KV cache transfer over 

135# RoCE only runs on nodes that carry all three. 

136EFA_RESOURCE_NAME = "vpc.amazonaws.com/efa" 

137EFA_NODE_SELECTOR_KEY = "efa" 

138EFA_NODE_SELECTOR_VALUE = "true" 

139 

140# Mooncake KV-transfer role pods are pinned to a dedicated EFA NodePool 

141# (mooncake-efa-pool, manifest 46-nodepool-mooncake-efa.yaml) that only offers 

142# instance families with >=80GB of GPU memory and FP8-capable Hopper/Blackwell 

143# GPUs. The shared training EFA pool (43-nodepool-efa.yaml) also offers p4d 

144# (A100 40GB, Ampere, no FP8), which is too small for many disaggregated/store 

145# models and can be selected by Karpenter whenever a pod asks only for efa=true. 

146# Selecting this extra label keeps role pods off p4d without disturbing the 

147# training pool. The value must match the label on the dedicated NodePool. 

148MOONCAKE_EFA_NODE_SELECTOR_KEY = "mooncake-efa" 

149MOONCAKE_EFA_NODE_SELECTOR_VALUE = "true" 

150 

151# The shared per-region Mooncake master exposes its RPC service and the 

152# built-in HTTP metadata server on these fixed ports. 

153MOONCAKE_MASTER_RPC_PORT = 50051 

154MOONCAKE_METADATA_PORT = 8080 

155MOONCAKE_MASTER_SERVICE = "mooncake-master" 

156 

157# GPU utilization is not a Kubernetes Resource metric, so a native 

158# HorizontalPodAutoscaler cannot scale on it (Resource metrics are limited to 

159# cpu and memory). The cluster's amazon-cloudwatch-observability agent publishes 

160# per-pod GPU utilization to CloudWatch ContainerInsights, so any autoscaler 

161# that requests a GPU metric is materialized as a KEDA ScaledObject with an 

162# aws-cloudwatch trigger instead. KEDA generates the backing HPA under the hood, 

163# and cpu/memory targets ride along as native cpu/memory triggers on the same 

164# ScaledObject. KEDA is a mandatory cluster component, so this path is always 

165# available. 

166KEDA_API_GROUP = "keda.sh" 

167KEDA_API_VERSION = "v1alpha1" 

168KEDA_SCALEDOBJECT_PLURAL = "scaledobjects" 

169 

170# Metric types that can only be served via CloudWatch (KEDA), keyed to the 

171# ContainerInsights metric the aws-cloudwatch trigger reads. PodName in the 

172# ContainerInsights dimension set is the workload (Deployment) name, so the 

173# dimension triple ClusterName/Namespace/PodName yields the average across a 

174# Deployment's pods — exactly the signal autoscaling needs. 

175GPU_METRIC_NAMESPACE = "ContainerInsights" 

176_CLOUDWATCH_METRIC_BY_TYPE = { 

177 "gpu": "pod_gpu_utilization", 

178 "gpu_memory": "pod_gpu_memory_utilization", 

179} 

180 

181# Default base port for the KV-transfer bootstrap handshake (VLLM_MOONCAKE_ 

182# BOOTSTRAP_PORT) and the span of per-worker ports derived from it. vLLM assigns 

183# each worker base_port + dp_rank * tp_size + tp_rank, so the intra-namespace 

184# allow rule opens a contiguous window starting at the base port. A spec may 

185# override the base via mooncake.transfer.bootstrap_base_port. 

186MOONCAKE_BOOTSTRAP_BASE_PORT = 8998 

187MOONCAKE_BOOTSTRAP_PORT_SPAN = 100 

188 

189# Environment variable through which each role pod receives the KV-transfer 

190# bootstrap base port. vLLM derives per-worker ports (base + dp_rank * tp_size + 

191# tp_rank) from it, so prefill and decode agree on the handshake ports. 

192VLLM_MOONCAKE_BOOTSTRAP_PORT_ENV = "VLLM_MOONCAKE_BOOTSTRAP_PORT" 

193 

194# Each role pod reads the shared transport settings (metadata-server address, 

195# protocol, device) from the rendered mooncake.json. The per-endpoint 

196# ``{name}-mooncake`` ConfigMap is mounted read-only at the directory below, and 

197# the connector is pointed at the file through MOONCAKE_CONFIG_PATH. 

198MOONCAKE_CONFIG_PATH_ENV = "MOONCAKE_CONFIG_PATH" 

199MOONCAKE_CONFIG_MOUNT_DIR = "/etc/mooncake" 

200MOONCAKE_CONFIG_FILE_PATH = f"{MOONCAKE_CONFIG_MOUNT_DIR}/mooncake.json" 

201 

202# Label selector identifying inference workload pods (prefill/decode/proxy and 

203# legacy single-Deployment endpoints). Used as both the target and the peer of 

204# the intra-namespace allow rules. 

205INFERENCE_POD_SELECTOR = {"gco.io/type": "inference"} 

206 

207 

208def _inference_service_selector(app: str, **extra: str) -> dict[str, str]: 

209 """The ``spec.selector`` of every ClusterIP Service fronting inference pods. 

210 

211 The selector carries ``gco.io/type: inference`` next to the per-endpoint 

212 ``app`` label, and that is load-bearing under enforced NetworkPolicies on 

213 the VPC CNI: its egress probe sees ClusterIP traffic before kube-proxy's 

214 DNAT, so the network policy controller admits a Service's ClusterIP only 

215 when the Service's own selector matches the policy peer's ``podSelector`` 

216 (``gco.io/type: inference`` in ``allow-inference-proxy-to-inference`` and 

217 ``allow-inference-internal``). With ``app`` alone the pods were admitted 

218 and the Service in front of them was not, and the inference proxy's 

219 requests timed out against a healthy endpoint. 

220 """ 

221 return {"app": app, **INFERENCE_POD_SELECTOR, **extra} 

222 

223 

224# Names of the intra-namespace allow rules the monitor maintains alongside the 

225# default-deny posture in gco-inference. These mirror the manifest names in 

226# 03-network-policies.yaml so a failure can point at the same object an operator 

227# would inspect with kubectl. 

228NETWORK_POLICY_INFERENCE_INTERNAL = "allow-inference-internal" 

229NETWORK_POLICY_POD_TO_MASTER = "allow-pod-to-master" 

230NETWORK_POLICY_POD_TO_METADATA = "allow-pod-to-metadata" 

231NETWORK_POLICY_RDMA_BOOTSTRAP = "allow-rdma-bootstrap" 

232 

233# Regional configuration keys through which the in-region deployment supplies 

234# the shared master's address. The store cannot be wired without an own-region 

235# master address, so a store-bearing endpoint defers configuration when the 

236# master address is absent or blank. The metadata server defaults to the master 

237# host on the metadata port when not supplied explicitly. 

238MOONCAKE_MASTER_ADDRESS_ENV = "MOONCAKE_MASTER_ADDRESS" 

239MOONCAKE_METADATA_SERVER_ENV = "MOONCAKE_METADATA_SERVER" 

240 

241# Container image for the shared per-region master. Supplied by the in-region 

242# deployment so the master tracks the same pinned build the manifests use. 

243MOONCAKE_MASTER_IMAGE_ENV = "MOONCAKE_MASTER_IMAGE" 

244 

245# Maximum time a store-bearing endpoint keeps deferring role-pod creation while 

246# the shared master has not reported a Ready replica. Past this window the 

247# monitor keeps deferring and stays in the ``creating`` state, but also surfaces 

248# an error so operators can see the master never came up. The master itself is 

249# never deleted or modified on account of this timeout. 

250MOONCAKE_MASTER_READY_TIMEOUT_SECONDS = 600 

251 

252# Object-key prefix under which cold-tier KV objects are written in the 

253# general-purpose regional bucket. Mirrors the value the regional stack and the 

254# `gco inference populate-kv` upload surface use; kept local so the monitor 

255# needs no infrastructure (CDK) imports at runtime. 

256MOONCAKE_COLD_TIER_KEY_PREFIX = "mooncake-kv" 

257 

258# SSM namespace suffix publishing the always-on general-purpose regional 

259# bucket's discovery values for a region. The full namespace is 

260# ``/<project_name>/regional-shared-bucket`` (see 

261# ``constants.regional_shared_ssm_parameter_prefix``); the monitor builds it at 

262# runtime from the injected ``PROJECT_NAME`` env var rather than importing the 

263# CDK constant, so it needs no infrastructure imports at runtime. Kept as a 

264# suffix constant so the ``/name``, ``/arn``, ``/region`` contract stays in one 

265# place. See ``_regional_shared_ssm_parameter_prefix``. 

266REGIONAL_SHARED_SSM_PARAMETER_SUFFIX = "regional-shared-bucket" 

267 

268 

269def _regional_shared_ssm_parameter_prefix() -> str: 

270 """Return this deployment's regional-shared-bucket SSM namespace. 

271 

272 Built from the ``PROJECT_NAME`` environment variable (default ``"gco"``) 

273 so the monitor reads the same project-scoped path the regional stack 

274 writes (``/<project_name>/regional-shared-bucket``). Mirrors 

275 ``constants.regional_shared_ssm_parameter_prefix`` without importing CDK. 

276 """ 

277 project_name = os.environ.get("PROJECT_NAME", "gco") 

278 return f"/{project_name}/{REGIONAL_SHARED_SSM_PARAMETER_SUFFIX}" 

279 

280 

281# Matches an AWS region identifier embedded in an address (host or URI), e.g. 

282# ``us-east-1``, ``eu-west-2``, ``ap-southeast-1``, ``us-gov-west-1``. KV 

283# transfer over RoCE is intra-region, so any address a topology wires to must 

284# resolve to the monitor's own region; an embedded token naming a different 

285# region marks the address as out-of-region. An address that carries no token 

286# (a bare in-cluster Service name) is region-local by construction. 

287_REGION_TOKEN_PATTERN = re.compile(r"\b[a-z]{2}-(?:gov-)?[a-z]+-\d+\b") 

288 

289# --- PD proxy behavior ------------------------------------------------------- 

290# 

291# The prefill-decode proxy that fronts a disaggregated endpoint checks whether a 

292# prompt's KV blocks already live in the shared store before it sends the prompt 

293# to a prefill pod. That check is bounded: it is given this many seconds, and a 

294# miss or a check that does not finish in time is treated as "not resident" so 

295# the prompt goes to prefill without the request waiting any longer. Holding the 

296# bound here keeps the proxy responsive even when the store is slow or 

297# unreachable. 

298PD_PROXY_RESIDENCY_TIMEOUT_SECONDS = 2 

299 

300# Default strategy for spreading requests across the backends of a single role. 

301PD_PROXY_DEFAULT_SCHEDULING = "round_robin" 

302 

303# Where a residency miss or timed-out lookup is sent. The prompt always goes to 

304# a prefill pod in that case; the proxy never stalls the request on the store. 

305PD_PROXY_RESIDENCY_MISS_TARGET = "prefill" 

306 

307# The residency lookup never blocks the request: a slow or failed store check 

308# falls through to prefill rather than holding the client. 

309PD_PROXY_RESIDENCY_BLOCKING = "false" 

310 

311# Decode-phase requests only ever reach decode pods that report Ready; pods that 

312# are still starting are skipped. 

313PD_PROXY_DECODE_ROUTING_READY_ONLY = "ready_only" 

314 

315# When no decode pod reports Ready, the proxy refuses the request outright 

316# instead of streaming a partial generation. The refusal carries a stable 

317# status and message so clients can distinguish "no backend yet" from a model 

318# error. 

319PD_PROXY_NO_DECODE_BACKEND_ACTION_REJECT = "reject" 

320PD_PROXY_NO_DECODE_BACKEND_STATUS = "503" 

321PD_PROXY_NO_DECODE_BACKEND_MESSAGE = "no available decode backend" 

322 

323# Environment variable names the proxy container reads to pick up the behavior 

324# above. Surfacing them here keeps the proxy's runtime contract in one place; 

325# the reconcile path attaches the values produced by ``build_pd_proxy_config``. 

326PD_PROXY_RESIDENCY_TIMEOUT_ENV = "PD_PROXY_RESIDENCY_TIMEOUT_SECONDS" 

327PD_PROXY_RESIDENCY_BLOCKING_ENV = "PD_PROXY_RESIDENCY_CHECK_BLOCKING" 

328PD_PROXY_RESIDENCY_MISS_TARGET_ENV = "PD_PROXY_RESIDENCY_MISS_TARGET" 

329PD_PROXY_DECODE_ROUTING_ENV = "PD_PROXY_DECODE_ROUTING" 

330PD_PROXY_NO_DECODE_BACKEND_ACTION_ENV = "PD_PROXY_NO_DECODE_BACKEND_ACTION" 

331PD_PROXY_NO_DECODE_BACKEND_STATUS_ENV = "PD_PROXY_NO_DECODE_BACKEND_STATUS" 

332PD_PROXY_NO_DECODE_BACKEND_MESSAGE_ENV = "PD_PROXY_NO_DECODE_BACKEND_MESSAGE" 

333PD_PROXY_SCHEDULING_ENV = "PD_PROXY_SCHEDULING" 

334PD_PROXY_STORE_ADDRESS_ENV = "PD_PROXY_STORE_ADDRESS" 

335 

336# Marker label carried by proxy pods so a Service can select the proxy alone, 

337# distinct from the prefill/decode role pods (which carry their own role marker). 

338PD_PROXY_ROLE_LABEL = "proxy" 

339 

340# TCP port the proxy container listens on for authenticated serving requests. 

341PD_PROXY_PORT = 8000 

342 

343# Environment variable the proxy reads its admin key from, and the data key the 

344# backing Kubernetes Secret stores it under. The key value is delivered to the 

345# container through a Secret reference at pod start — it is never written to the 

346# endpoint spec or passed as a command-line argument. 

347PD_PROXY_ADMIN_API_KEY_ENV = "ADMIN_API_KEY" 

348ADMIN_API_KEY_SECRET_DATA_KEY = "ADMIN_API_KEY" 

349 

350# The proxy program (gco/services/mooncake_pd_proxy.py) is shipped to the proxy 

351# pod as a ConfigMap and run from this mount path. The prefill/decode backend 

352# URLs and the listen port are passed to it through these env vars; it routes to 

353# the role pods through their in-cluster Services so kube-proxy load-balances 

354# across only the Ready endpoints of each role. 

355PD_PROXY_SCRIPT_FILENAME = "mooncake_pd_proxy.py" 

356PD_PROXY_CONFIG_MOUNT_DIR = "/etc/pd-proxy" 

357PD_PROXY_SCRIPT_PATH = f"{PD_PROXY_CONFIG_MOUNT_DIR}/{PD_PROXY_SCRIPT_FILENAME}" 

358PD_PROXY_PORT_ENV = "PD_PROXY_PORT" 

359PD_PROXY_PREFILL_URL_ENV = "PD_PROXY_PREFILL_URL" 

360PD_PROXY_DECODE_URL_ENV = "PD_PROXY_DECODE_URL" 

361 

362 

363@dataclass 

364class RegionServicesResolution: 

365 """Outcome of resolving the in-region service addresses an endpoint needs. 

366 

367 ``render_mooncake_config`` consumes already-resolved values via a 

368 ``region_services`` dict; this carries that dict together with the signals 

369 a reconcile pass acts on: 

370 

371 - ``region_services`` is the resolved dict to render with, or ``None`` when 

372 rendering must be skipped. 

373 - ``render_skipped`` is set when the store is enabled but the own-region 

374 master address is not configured: the existing endpoint configuration is 

375 left untouched and ``store_master_unresolved`` records why. 

376 - ``cold_tier_unresolved`` is set when the cold tier was requested but the 

377 own-region general-purpose bucket could not be resolved; the cold tier is 

378 dropped while the hot-path store keeps operating, and ``error`` explains 

379 the condition. 

380 """ 

381 

382 region_services: dict[str, Any] | None = None 

383 render_skipped: bool = False 

384 store_master_unresolved: bool = False 

385 cold_tier_unresolved: bool = False 

386 error: str | None = None 

387 

388 

389@dataclass 

390class MasterReadinessGate: 

391 """Outcome of gating dependent role-pod creation on the shared master. 

392 

393 A store-bearing endpoint must not materialize its role pods until the 

394 single shared ``mooncake-master`` reports a Ready replica. This carries the 

395 decision a reconcile pass acts on: 

396 

397 - ``proceed`` is ``True`` only when the master reports at least one Ready 

398 replica; the caller may then create the dependent role pods and advance 

399 out of ``creating``. While it is ``False`` the caller materializes no 

400 dependent pods. 

401 - ``state`` is the endpoint state to report. It is ``"creating"`` whenever 

402 creation is deferred (master not ready, still within the wait window, 

403 timed out, or could not be created) and ``None`` when the gate is open. 

404 - ``error`` records why creation could not advance: the master did not 

405 become Ready within the wait window, or the master could not be created. 

406 It is ``None`` while the master is simply still coming up within the 

407 window, and ``None`` once the gate is open. 

408 """ 

409 

410 proceed: bool = False 

411 state: str | None = None 

412 error: str | None = None 

413 

414 

415@dataclass 

416class RegionalScopeResolution: 

417 """Outcome of confirming a disaggregated topology stays inside one region. 

418 

419 KV cache transfer over RoCE cannot cross a region boundary, so every 

420 ``MooncakeConnector`` peer address and the ``master_server_address`` a 

421 topology wires to must resolve to the monitor's own region. An endpoint 

422 that targets several regions runs one independent topology per region; each 

423 region's monitor reconciles only its own topology and confirms that 

424 topology's addresses never escape the region. 

425 

426 - ``in_region`` is ``True`` only when every resolved address belongs to the 

427 monitor's own region. While it is ``True`` the caller may materialize the 

428 topology's role Deployments. 

429 - ``peer_addresses`` lists the addresses that were resolved and checked, in 

430 a stable order, so callers and logs can show exactly what was wired. 

431 - ``state`` is the endpoint state to report. It is ``"failed"`` when an 

432 out-of-region address is detected and ``None`` when the topology is 

433 wholly in-region. 

434 - ``error`` describes the cross-region boundary violation — which addresses 

435 resolved to which other regions — when one is found, and is ``None`` 

436 otherwise. When a violation is reported the caller materializes no role 

437 Deployments and leaves any previously materialized resources unchanged. 

438 """ 

439 

440 in_region: bool = True 

441 peer_addresses: list[str] = field(default_factory=list) 

442 state: str | None = None 

443 error: str | None = None 

444 

445 

446class RegionStatusConditions(TypedDict, total=False): 

447 """Optional DynamoDB predicates shared by every regional status write.""" 

448 

449 expected_lifecycle_id: str 

450 expected_region_generation: str 

451 expected_deletion_generation: str 

452 

453 

454_LIFECYCLE_ANNOTATION = "gco.io/lifecycle-id" 

455_REGION_GENERATION_ANNOTATION = "gco.io/region-generation" 

456_LEADER_EPOCH_ANNOTATION = "gco.io/leader-epoch" 

457 

458 

459class ReconcileFencedError(RuntimeError): 

460 """The caller no longer owns the endpoint or leader epoch it read.""" 

461 

462 

463@dataclass(frozen=True) 

464class ReconcileAuthority: 

465 """Immutable authority carried by one endpoint reconciliation pass.""" 

466 

467 endpoint_name: str 

468 lifecycle_id: str 

469 region_generation: str 

470 leader_epoch: str 

471 deletion_generation: str | None = None 

472 deleting: bool = False 

473 region_removed: bool = False 

474 

475 @property 

476 def annotations(self) -> dict[str, str]: 

477 return { 

478 _LIFECYCLE_ANNOTATION: self.lifecycle_id, 

479 _REGION_GENERATION_ANNOTATION: self.region_generation, 

480 _LEADER_EPOCH_ANNOTATION: self.leader_epoch, 

481 } 

482 

483 

484@dataclass(frozen=True) 

485class EndpointResourceInventory: 

486 """Deterministic names of every top-level Kubernetes object owned by an endpoint.""" 

487 

488 deployments: tuple[str, ...] 

489 services: tuple[str, ...] 

490 horizontal_pod_autoscalers: tuple[str, ...] 

491 scaled_objects: tuple[str, ...] 

492 config_maps: tuple[str, ...] 

493 legacy_ingresses: tuple[str, ...] 

494 legacy_http_routes: tuple[str, ...] 

495 generated_admin_secret: str 

496 

497 

498@dataclass(frozen=True) 

499class ResourceCleanupResult: 

500 """Observed result of one idempotent endpoint cleanup pass. 

501 

502 A successful Kubernetes delete only starts asynchronous deletion. ``pending`` 

503 therefore contains objects still returned by a read-after-delete, while 

504 ``errors`` contains sanitized request failures. Cleanup is complete only 

505 when every owned object is independently observed absent and no request 

506 failed during the pass. 

507 """ 

508 

509 pending: tuple[str, ...] = () 

510 errors: tuple[str, ...] = () 

511 resources_found: bool = False 

512 

513 @property 

514 def complete(self) -> bool: 

515 return not self.pending and not self.errors 

516 

517 @property 

518 def error_message(self) -> str | None: 

519 if not self.errors: 

520 return None 

521 return "Endpoint cleanup could not be completed: " + "; ".join(self.errors) 

522 

523 

524def _resolved_mooncake_transfer(mooncake: dict[str, Any]) -> tuple[str, str]: 

525 """Resolve the persisted transfer intent and optional network device. 

526 

527 GCO's spec deliberately uses ``rdma`` as the portable high-performance 

528 intent because the mounted Mooncake store configuration accepts 

529 ``rdma|tcp``. On AWS, role pods with that intent are placed on EFA nodes, 

530 so :func:`build_kv_transfer_config` translates it to vLLM's explicit 

531 ``mooncake_protocol=efa`` at the point-to-point connector boundary. 

532 """ 

533 transfer = mooncake.get("transfer", {}) 

534 if not isinstance(transfer, dict): 

535 raise ValueError("mooncake.transfer must be a mapping") 

536 

537 protocol = transfer.get("protocol", "rdma") 

538 if protocol not in {"rdma", "tcp"}: 

539 raise ValueError( 

540 f"mooncake.transfer.protocol must be one of {{rdma, tcp}}, got {protocol!r}" 

541 ) 

542 device_name = transfer.get("device_name", "") 

543 if not isinstance(device_name, str): 

544 raise ValueError(f"mooncake.transfer.device_name must be a string, got {device_name!r}") 

545 return protocol, device_name 

546 

547 

548def build_kv_transfer_config(mooncake: dict[str, Any], role: str) -> str: 

549 """Return the JSON string for vLLM's ``--kv-transfer-config``. 

550 

551 Translates a mooncake spec block plus a worker role into the connector 

552 configuration vLLM expects: 

553 

554 - ``disaggregated`` emits a ``MooncakeConnector``. 

555 - ``store`` emits a ``MooncakeStoreConnector``. 

556 - ``both`` emits a ``MultiConnector`` wrapping a ``MooncakeConnector`` 

557 (index 0) followed by a ``MooncakeStoreConnector`` (index 1), both 

558 sharing the role's ``kv_role``. 

559 

560 Every point-to-point ``MooncakeConnector`` receives explicit 

561 ``kv_connector_extra_config``. GCO's default/high-performance ``rdma`` 

562 intent maps to Mooncake's AWS-specific ``efa`` protocol because the same 

563 pod is pinned to the EFA node pool; ``tcp`` remains an explicit fallback. 

564 ``device_name`` is forwarded verbatim, with an empty string requesting 

565 Mooncake/libfabric auto-detection. 

566 

567 The emitted ``kv_role`` is ``kv_producer`` for prefill, ``kv_consumer`` 

568 for decode, and ``kv_both`` for a single store instance. 

569 

570 Args: 

571 mooncake: The ``spec["mooncake"]`` block; its ``mode`` selects the 

572 connector shape and its optional ``transfer`` block selects the 

573 protocol/device. 

574 role: One of ``"prefill"``, ``"decode"``, or ``"single"``. 

575 

576 Returns: 

577 A JSON object string parseable by vLLM. 

578 

579 Raises: 

580 ValueError: If the ``(mode, role)`` combination or transfer settings 

581 are unsupported. No configuration is emitted in that case. 

582 """ 

583 mode = mooncake.get("mode") 

584 supported_roles = _WORKER_ROLES_BY_MODE.get(mode) if isinstance(mode, str) else None 

585 if supported_roles is None or role not in supported_roles: 

586 raise ValueError(f"Unsupported (mode, role) pair: ({mode!r}, {role!r})") 

587 

588 kv_role = _KV_ROLE_BY_WORKER_ROLE[role] 

589 

590 if mode == "store": 

591 return json.dumps({"kv_connector": "MooncakeStoreConnector", "kv_role": kv_role}) 

592 

593 protocol, device_name = _resolved_mooncake_transfer(mooncake) 

594 connector = { 

595 "kv_connector": "MooncakeConnector", 

596 "kv_role": kv_role, 

597 "kv_connector_extra_config": { 

598 "mooncake_protocol": "efa" if protocol == "rdma" else "tcp", 

599 "device_name": device_name, 

600 }, 

601 } 

602 if mode == "disaggregated": 

603 return json.dumps(connector) 

604 

605 # mode == "both": MultiConnector chains transfer then store. 

606 return json.dumps( 

607 { 

608 "kv_connector": "MultiConnector", 

609 "kv_role": kv_role, 

610 "kv_connector_extra_config": { 

611 "connectors": [ 

612 connector, 

613 {"kv_connector": "MooncakeStoreConnector", "kv_role": kv_role}, 

614 ] 

615 }, 

616 } 

617 ) 

618 

619 

620def bootstrap_port_for_worker(base_port: int, dp_rank: int, tp_size: int, tp_rank: int) -> int: 

621 """Compute the bootstrap port for a ``(dp_rank, tp_rank)`` worker. 

622 

623 The port is ``base_port + dp_rank * tp_size + tp_rank``. For a fixed 

624 ``base_port`` and ``tp_size`` distinct ``(dp_rank, tp_rank)`` pairs map to 

625 distinct ports. 

626 

627 Args: 

628 base_port: The base bootstrap port for the endpoint. 

629 dp_rank: The data-parallel rank of the worker (``>= 0``). 

630 tp_size: The tensor-parallel world size (``>= 1``). 

631 tp_rank: The tensor-parallel rank within the worker (``0 <= tp_rank < tp_size``). 

632 

633 Returns: 

634 The TCP port assigned to the worker. 

635 

636 Raises: 

637 ValueError: If the computed port falls outside the valid range 

638 ``1024..65535``. No port is assigned in that case. 

639 """ 

640 port = base_port + dp_rank * tp_size + tp_rank 

641 if port < MIN_BOOTSTRAP_PORT or port > MAX_BOOTSTRAP_PORT: 

642 raise ValueError( 

643 f"Computed bootstrap port {port} is outside the valid range " 

644 f"{MIN_BOOTSTRAP_PORT}..{MAX_BOOTSTRAP_PORT}" 

645 ) 

646 return port 

647 

648 

649def render_mooncake_config( 

650 mooncake: dict[str, Any], region_services: dict[str, Any] 

651) -> dict[str, Any]: 

652 """Render the ``mooncake.json`` contents mounted into each vLLM pod. 

653 

654 The returned dict is written verbatim to a ConfigMap and mounted at the 

655 path named by ``MOONCAKE_CONFIG_PATH``. It always carries the metadata 

656 server and the RDMA/TCP transport settings (``protocol`` and 

657 ``device_name``); the key-value store and its optional cold tier are layered 

658 on only when requested. 

659 

660 The transport block (``protocol``/``device_name``) describes the hot 

661 RDMA/RoCE path. The cold tier is an asynchronous object-store backend keyed 

662 separately as ``cold_tier_s3_uri``; it is never wired into the transport 

663 block, so cold-tier reads and writes stay off the RDMA hot path. 

664 

665 Resolution of in-region addresses is the caller's responsibility: this 

666 function consumes already-resolved values from ``region_services`` and 

667 performs no lookups of its own. In particular, the cold-tier URI is the 

668 monitor-resolved general-purpose regional bucket for the monitor's own 

669 region; any cold-tier bucket URI in the user spec is ignored. 

670 

671 Args: 

672 mooncake: The ``spec["mooncake"]`` block. 

673 region_services: Resolved in-region addresses, e.g.:: 

674 

675 { 

676 "metadata_server": "http://mooncake-master:8080/metadata", 

677 "master_server_address": "mooncake-master:50051", 

678 "cold_tier_s3_uri": "s3://gco-regional-shared-<acct>-<region>/...", 

679 } 

680 

681 ``master_server_address`` is required when the store is enabled and 

682 ``cold_tier_s3_uri`` is required when the cold tier is enabled. 

683 

684 Returns: 

685 The ``mooncake.json`` contents as a dict, where: 

686 

687 - ``protocol`` and ``device_name`` are always present. 

688 - ``master_server_address`` is present only when the store is enabled. 

689 - ``cold_tier_s3_uri`` is present only when the cold tier is enabled, 

690 which requires ``cold_tier_enabled`` to be the boolean ``True``; any 

691 other value (absent, null, truthy non-bool) leaves the cold tier off. 

692 """ 

693 protocol, device_name = _resolved_mooncake_transfer(mooncake) 

694 store = mooncake.get("store", {}) 

695 cfg: dict[str, Any] = { 

696 "metadata_server": region_services["metadata_server"], 

697 "protocol": protocol, 

698 "device_name": device_name, 

699 } 

700 if store.get("enabled"): 

701 cfg["master_server_address"] = region_services["master_server_address"] 

702 # The store runs embedded in each vLLM pod (every rank contributes 

703 # `global_segment_size` to the shared pool; GCO's per-region 

704 # mooncake-master is only the metadata/master coordinator, not a 

705 # standalone store that owns the pool). Embedded mode rejects a zero 

706 # segment, so default to 4 GiB (the upstream default) when the spec 

707 # does not set one; an operator can tune it via configure-store. 

708 cfg["global_segment_size"] = store.get("global_segment_size", "4294967296") 

709 cfg["local_buffer_size"] = store.get("local_buffer_size", "2147483648") 

710 # Only the boolean True enables the cold tier; any other value leaves it 

711 # off. The URI is resolved by the caller for the monitor's own region — 

712 # never authored by the user — and is an object-store backend kept off 

713 # the RDMA transport block above. 

714 if store.get("cold_tier_enabled") is True: 

715 cfg["cold_tier_s3_uri"] = region_services["cold_tier_s3_uri"] 

716 return cfg 

717 

718 

719def apply_efa_scheduling(mooncake: dict[str, Any], pod_spec: client.V1PodSpec) -> None: 

720 """Place a role pod on the EFA RDMA fabric when transfer runs over RDMA. 

721 

722 KV cache transfer over RoCE only runs on EFA-enabled nodes, which carry a 

723 ``vpc.amazonaws.com/efa`` taint, advertise the ``vpc.amazonaws.com/efa`` 

724 extended resource, and are labelled ``efa=true``. When the transfer 

725 protocol is ``rdma`` this mutates ``pod_spec`` in place to: 

726 

727 - add a ``vpc.amazonaws.com/efa`` toleration (in addition to any existing 

728 tolerations such as the GPU one), 

729 - add an ``efa=true`` node selector plus a ``mooncake-efa=true`` node 

730 selector (merged with any existing selectors), and 

731 - request at least one ``vpc.amazonaws.com/efa`` device on the pod's 

732 containers, leaving every existing resource request and limit — including 

733 GPU asks — untouched. 

734 

735 The ``mooncake-efa=true`` selector pins the pod to the dedicated 

736 ``mooncake-efa-pool`` NodePool, which only offers instance families with 

737 >=80GB of GPU memory and FP8-capable Hopper/Blackwell GPUs. This keeps role 

738 pods off the A100-40GB ``p4d`` family that the shared training EFA pool 

739 still offers — that family OOMs on many models and cannot run FP8 KV-cache 

740 configs, so Karpenter selecting it for a mooncake pod is a latent failure. 

741 

742 When the transfer protocol is explicitly set to anything other than 

743 ``rdma`` (for example ``tcp``) the pod is left exactly as it was: no 

744 toleration, no node selector, and no device request are added. An unset 

745 protocol defaults to ``rdma`` — matching the rest of the Mooncake path — so 

746 a disaggregated endpoint lands on EFA by default. 

747 

748 Tolerations, selectors, and device requests are applied idempotently, so 

749 re-running over an already-scheduled pod produces no duplicates. 

750 

751 Args: 

752 mooncake: The ``spec["mooncake"]`` block; ``transfer.protocol`` 

753 (defaulting to ``rdma`` when unset) decides whether EFA scheduling 

754 applies. 

755 pod_spec: The pod specification to mutate in place. 

756 """ 

757 protocol, _device_name = _resolved_mooncake_transfer(mooncake) 

758 if protocol != "rdma": 

759 return 

760 

761 # Tolerate the EFA taint without disturbing existing tolerations. 

762 tolerations = list(pod_spec.tolerations or []) 

763 if not any(t.key == EFA_RESOURCE_NAME for t in tolerations): 

764 tolerations.append( 

765 client.V1Toleration( 

766 key=EFA_RESOURCE_NAME, 

767 operator="Equal", 

768 value="true", 

769 effect="NoSchedule", 

770 ) 

771 ) 

772 pod_spec.tolerations = tolerations 

773 

774 # Merge the EFA node selectors with any selectors already in place. The 

775 # generic efa=true selector lands the pod on EFA fabric; mooncake-efa=true 

776 # narrows that to the dedicated mooncake-efa-pool, which excludes the 

777 # A100-40GB p4d family that the shared training EFA pool still offers. 

778 node_selector = dict(pod_spec.node_selector or {}) 

779 node_selector[EFA_NODE_SELECTOR_KEY] = EFA_NODE_SELECTOR_VALUE 

780 node_selector[MOONCAKE_EFA_NODE_SELECTOR_KEY] = MOONCAKE_EFA_NODE_SELECTOR_VALUE 

781 pod_spec.node_selector = node_selector 

782 

783 # Request at least one EFA device, preserving existing requests and limits 

784 # (notably the GPU asks). Apply to containers that already request an 

785 # accelerator; if none do, apply to every container so the pod still asks 

786 # for the fabric it needs. 

787 containers = pod_spec.containers or [] 

788 accelerator_keys = ("nvidia.com/gpu", "aws.amazon.com/neuron") 

789 

790 def _requests_accelerator(container: client.V1Container) -> bool: 

791 reqs = container.resources 

792 if reqs is None: 

793 return False 

794 for table in (reqs.requests, reqs.limits): 

795 if table and any(key in table for key in accelerator_keys): 

796 return True 

797 return False 

798 

799 targets = [c for c in containers if _requests_accelerator(c)] or list(containers) 

800 for container in targets: 

801 if container.resources is None: 

802 container.resources = client.V1ResourceRequirements() 

803 if container.resources.requests is None: 

804 container.resources.requests = {} 

805 if container.resources.limits is None: 

806 container.resources.limits = {} 

807 container.resources.requests.setdefault(EFA_RESOURCE_NAME, "1") 

808 container.resources.limits.setdefault(EFA_RESOURCE_NAME, "1") 

809 

810 

811def build_pd_proxy_config(mooncake: dict[str, Any]) -> dict[str, str]: 

812 """Return the environment the prefill-decode proxy runs with. 

813 

814 The proxy fronts a disaggregated endpoint and decides, per request, whether 

815 to consult the shared store and which backends to dispatch to. Its behavior 

816 is fixed by the values returned here so every disaggregated endpoint front 

817 behaves identically: 

818 

819 - It looks up whether the prompt's KV blocks already reside in the store 

820 before sending the prompt to prefill, and that lookup is bounded to 

821 ``PD_PROXY_RESIDENCY_TIMEOUT_SECONDS`` seconds. 

822 - A miss, or a lookup that does not finish in time, is treated as "not 

823 resident": the prompt is sent to a prefill pod and the request is never 

824 held waiting on the store. 

825 - Decode-phase requests are routed only to decode pods reporting Ready, so a 

826 pod that is still starting is skipped. 

827 - When no decode pod reports Ready, the proxy refuses the request with a 

828 stable status and message rather than streaming any partial output. 

829 

830 The residency bound is held constant rather than read from the spec so the 

831 responsiveness guarantee cannot be weakened per endpoint. The store address 

832 points at the shared in-region master, and the same-role dispatch strategy 

833 falls back to round-robin when the spec names none. 

834 

835 Args: 

836 mooncake: The ``spec["mooncake"]`` block; its optional ``proxy`` section 

837 supplies the same-role scheduling strategy. 

838 

839 Returns: 

840 A mapping of environment variable name to value, ready to attach to the 

841 proxy container. 

842 """ 

843 proxy = mooncake.get("proxy", {}) or {} 

844 scheduling = proxy.get("scheduling") or PD_PROXY_DEFAULT_SCHEDULING 

845 store_address = f"{MOONCAKE_MASTER_SERVICE}:{MOONCAKE_MASTER_RPC_PORT}" 

846 return { 

847 PD_PROXY_RESIDENCY_TIMEOUT_ENV: str(PD_PROXY_RESIDENCY_TIMEOUT_SECONDS), 

848 PD_PROXY_RESIDENCY_BLOCKING_ENV: PD_PROXY_RESIDENCY_BLOCKING, 

849 PD_PROXY_RESIDENCY_MISS_TARGET_ENV: PD_PROXY_RESIDENCY_MISS_TARGET, 

850 PD_PROXY_DECODE_ROUTING_ENV: PD_PROXY_DECODE_ROUTING_READY_ONLY, 

851 PD_PROXY_NO_DECODE_BACKEND_ACTION_ENV: PD_PROXY_NO_DECODE_BACKEND_ACTION_REJECT, 

852 PD_PROXY_NO_DECODE_BACKEND_STATUS_ENV: PD_PROXY_NO_DECODE_BACKEND_STATUS, 

853 PD_PROXY_NO_DECODE_BACKEND_MESSAGE_ENV: PD_PROXY_NO_DECODE_BACKEND_MESSAGE, 

854 PD_PROXY_SCHEDULING_ENV: scheduling, 

855 PD_PROXY_STORE_ADDRESS_ENV: store_address, 

856 } 

857 

858 

859class InferenceMonitor: 

860 """ 

861 Reconciliation controller for inference endpoints. 

862 

863 Polls DynamoDB for desired endpoint state and reconciles with 

864 the actual Kubernetes resources in the local cluster. 

865 """ 

866 

867 def __init__( 

868 self, 

869 cluster_id: str, 

870 region: str, 

871 store: InferenceEndpointStore, 

872 namespace: str = "gco-inference", 

873 reconcile_interval: int = 15, 

874 ): 

875 self.cluster_id = cluster_id 

876 self.region = region 

877 self.store = store 

878 self.namespace = namespace 

879 self.reconcile_interval = reconcile_interval 

880 self._running = False 

881 

882 # Initialize Kubernetes clients 

883 try: 

884 config.load_incluster_config() 

885 logger.info("Loaded in-cluster Kubernetes configuration") 

886 except config.ConfigException: 

887 try: 

888 config.load_kube_config() 

889 logger.info("Loaded local Kubernetes configuration") 

890 except config.ConfigException as e: 

891 logger.error("Failed to load Kubernetes configuration: %s", e) 

892 raise 

893 

894 self.apps_v1 = client.AppsV1Api() 

895 self.core_v1 = client.CoreV1Api() 

896 self.networking_v1 = client.NetworkingV1Api() 

897 self.discovery_v1 = client.DiscoveryV1Api() 

898 

899 # Timeout for Kubernetes API calls (seconds) 

900 self._k8s_timeout = int(os.environ.get("K8S_API_TIMEOUT", "30")) 

901 

902 # Health watchdog: tracks when each endpoint first became unready. 

903 # Inference traffic enters through the shared ``gco-system/gco-gateway`` 

904 # HTTPRoute at ``/inference`` and then ``gco-system/inference-proxy``, so 

905 # model readiness never mutates shared Gateway API resources. Once this 

906 # threshold is exceeded, reconciliation emits an explicit degraded-state 

907 # warning while the proxy continues returning 503 until a replica is ready. 

908 self._unready_since: dict[str, datetime] = {} 

909 self._unhealthy_threshold_seconds = int( 

910 os.environ.get("INFERENCE_UNHEALTHY_THRESHOLD_SECONDS", "300") 

911 ) # 5 minutes default 

912 

913 # Master-readiness gate: tracks when each store-bearing endpoint first 

914 # deferred its role-pod creation because the shared master was not yet 

915 # Ready. The entry is cleared once the master reports a Ready replica so 

916 # a later restart of the master restarts the clock cleanly. 

917 self._master_deferral_since: dict[str, datetime] = {} 

918 

919 # Leader authority is renewed from a dedicated thread while reconcile 

920 # performs synchronous Kubernetes calls. The per-acquisition epoch is 

921 # also stamped on endpoint-owned objects; object UID/resourceVersion 

922 # preconditions remain the hard fence if a process resumes after losing 

923 # the Lease. 

924 self._lease_name: str | None = None 

925 self._lease_holder: str | None = None 

926 self._leader_epoch: str | None = None 

927 self._leadership_lost = threading.Event() 

928 self._active_authority: ReconcileAuthority | None = None 

929 

930 # Metrics 

931 self._reconcile_count = 0 

932 self._errors_count = 0 

933 # Monotonic stamp of the last completed loop iteration (leader pass or 

934 # standby lease check). Exported as seconds_since_last_pass so a loop 

935 # that is wedged — not crashed, which the probes would catch — is 

936 # visible; initialised at construction so the gauge exists before the 

937 # first iteration and grows if the loop never starts. 

938 self._last_loop_completed_at = time.monotonic() 

939 

940 # ------------------------------------------------------------------ 

941 # Reconciliation loop 

942 # ------------------------------------------------------------------ 

943 

944 async def start(self) -> None: 

945 """Start reconciliation under an independently renewed leader Lease.""" 

946 if self._running: 

947 logger.warning("Inference monitor already running") 

948 return 

949 self._running = True 

950 logger.info( 

951 "Starting inference monitor for %s in %s (interval=%ds)", 

952 self.cluster_id, 

953 self.region, 

954 self.reconcile_interval, 

955 ) 

956 

957 pod_name = os.environ.get("HOSTNAME", f"monitor-{id(self)}") 

958 lease_name = "inference-monitor-leader" 

959 self._lease_name = lease_name 

960 self._lease_holder = pod_name 

961 

962 while self._running: 

963 try: 

964 if self._try_acquire_lease(lease_name, pod_name): 

965 with self._renewing_leadership(): 

966 await self.reconcile() 

967 else: 

968 logger.debug("Not the leader, waiting...") 

969 except ReconcileFencedError as error: 

970 logger.warning("Reconciliation stopped after authority loss: %s", error) 

971 except Exception as error: 

972 logger.error("Reconciliation error: %s", error, exc_info=True) 

973 self._errors_count += 1 

974 self._last_loop_completed_at = time.monotonic() 

975 try: 

976 await asyncio.sleep(self.reconcile_interval) 

977 except Exception as error: 

978 logger.error("Sleep interrupted: %s", error) 

979 break 

980 

981 @staticmethod 

982 def _lease_annotations(lease: Any) -> dict[str, str]: 

983 metadata = getattr(lease, "metadata", None) 

984 annotations = getattr(metadata, "annotations", None) 

985 return dict(annotations) if isinstance(annotations, dict) else {} 

986 

987 def _lease_is_expired(self, lease: Any, now: datetime) -> bool: 

988 renew_time = getattr(getattr(lease, "spec", None), "renew_time", None) 

989 if renew_time is None: 

990 return True 

991 if renew_time.tzinfo is None: 

992 renew_time = renew_time.replace(tzinfo=UTC) 

993 duration = getattr(lease.spec, "lease_duration_seconds", None) 

994 if not isinstance(duration, int) or isinstance(duration, bool) or duration <= 0: 

995 duration = self.reconcile_interval * 3 

996 return bool((now - renew_time).total_seconds() >= int(duration)) 

997 

998 def _stamp_lease_epoch(self, lease: Any, epoch: str) -> None: 

999 metadata = getattr(lease, "metadata", None) 

1000 if metadata is None: 

1001 lease.metadata = client.V1ObjectMeta() 

1002 metadata = lease.metadata 

1003 annotations = self._lease_annotations(lease) 

1004 annotations[_LEADER_EPOCH_ANNOTATION] = epoch 

1005 metadata.annotations = annotations 

1006 

1007 def _try_acquire_lease(self, lease_name: str, holder: str) -> bool: 

1008 """Acquire/renew a Lease and establish one stable acquisition epoch.""" 

1009 coordination_v1 = client.CoordinationV1Api() 

1010 now = datetime.now(UTC) 

1011 lease_duration = self.reconcile_interval * 3 

1012 try: 

1013 lease = coordination_v1.read_namespaced_lease(lease_name, self.namespace) 

1014 current_holder = lease.spec.holder_identity 

1015 expired = self._lease_is_expired(lease, now) 

1016 annotations = self._lease_annotations(lease) 

1017 observed_epoch = annotations.get(_LEADER_EPOCH_ANNOTATION) 

1018 

1019 if current_holder == holder and not expired: 

1020 # Adopt the persisted epoch after a harmless local restart of 

1021 # the loop, but never renew a same-name holder with a different 

1022 # in-memory epoch. 

1023 if self._leader_epoch is None: 

1024 self._leader_epoch = observed_epoch or secrets.token_hex(32) 

1025 elif observed_epoch and observed_epoch != self._leader_epoch: 

1026 self._leadership_lost.set() 

1027 return False 

1028 epoch = self._leader_epoch 

1029 elif current_holder in (None, "") or expired: 

1030 epoch = secrets.token_hex(32) 

1031 self._leader_epoch = epoch 

1032 lease.spec.holder_identity = holder 

1033 lease.spec.acquire_time = now 

1034 transitions = getattr(lease.spec, "lease_transitions", None) 

1035 lease.spec.lease_transitions = int(transitions or 0) + 1 

1036 logger.info("Acquiring leader lease as %s with a new epoch", holder) 

1037 else: 

1038 return False 

1039 

1040 lease.spec.renew_time = now 

1041 lease.spec.lease_duration_seconds = lease_duration 

1042 self._stamp_lease_epoch(lease, epoch) 

1043 coordination_v1.replace_namespaced_lease(lease_name, self.namespace, lease) 

1044 self._lease_name = lease_name 

1045 self._lease_holder = holder 

1046 self._leadership_lost.clear() 

1047 return True 

1048 except ApiException as error: 

1049 if error.status == 404: 

1050 epoch = secrets.token_hex(32) 

1051 lease = client.V1Lease( 

1052 metadata=client.V1ObjectMeta( 

1053 name=lease_name, 

1054 namespace=self.namespace, 

1055 annotations={_LEADER_EPOCH_ANNOTATION: epoch}, 

1056 ), 

1057 spec=client.V1LeaseSpec( 

1058 holder_identity=holder, 

1059 lease_duration_seconds=lease_duration, 

1060 acquire_time=now, 

1061 renew_time=now, 

1062 lease_transitions=0, 

1063 ), 

1064 ) 

1065 try: 

1066 coordination_v1.create_namespaced_lease(self.namespace, lease) 

1067 except ApiException: 

1068 return False 

1069 self._leader_epoch = epoch 

1070 self._lease_name = lease_name 

1071 self._lease_holder = holder 

1072 self._leadership_lost.clear() 

1073 logger.info("Created leader lease as %s", holder) 

1074 return True 

1075 if error.status == 409: 

1076 logger.info("Lost leader Lease optimistic-concurrency race") 

1077 self._leadership_lost.set() 

1078 return False 

1079 logger.warning("Lease check failed: %s", error.reason) 

1080 self._leadership_lost.set() 

1081 return False 

1082 

1083 def _renew_current_lease(self) -> bool: 

1084 """Renew only the exact holder/epoch currently owned by this process.""" 

1085 lease_name = self._lease_name 

1086 holder = self._lease_holder 

1087 epoch = self._leader_epoch 

1088 if not all(isinstance(value, str) and value for value in (lease_name, holder, epoch)): 

1089 return False 

1090 coordination_v1 = client.CoordinationV1Api() 

1091 now = datetime.now(UTC) 

1092 try: 

1093 lease = coordination_v1.read_namespaced_lease(lease_name, self.namespace) 

1094 if ( 

1095 lease.spec.holder_identity != holder 

1096 or self._lease_annotations(lease).get(_LEADER_EPOCH_ANNOTATION) != epoch 

1097 or self._lease_is_expired(lease, now) 

1098 ): 

1099 return False 

1100 lease.spec.renew_time = now 

1101 coordination_v1.replace_namespaced_lease(lease_name, self.namespace, lease) 

1102 return True 

1103 except Exception: 

1104 logger.warning("Leader Lease renewal failed", exc_info=True) 

1105 return False 

1106 

1107 def _lease_renewal_loop(self, stop_event: threading.Event) -> None: 

1108 interval = max(1.0, float(self.reconcile_interval)) 

1109 while not stop_event.wait(interval): 

1110 if not self._renew_current_lease(): 

1111 self._leadership_lost.set() 

1112 return 

1113 

1114 @contextlib.contextmanager 

1115 def _renewing_leadership(self) -> Iterator[None]: 

1116 """Renew the Lease outside the asyncio loop while one pass is running.""" 

1117 stop_event = threading.Event() 

1118 renewal = threading.Thread( 

1119 target=self._lease_renewal_loop, 

1120 args=(stop_event,), 

1121 name="inference-monitor-lease-renewer", 

1122 daemon=True, 

1123 ) 

1124 renewal.start() 

1125 try: 

1126 yield 

1127 finally: 

1128 stop_event.set() 

1129 renewal.join(timeout=max(1.0, float(self.reconcile_interval))) 

1130 

1131 def _assert_current_leadership(self) -> None: 

1132 """Fail closed before mutation when the persisted Lease epoch changed.""" 

1133 if self._leadership_lost.is_set(): 

1134 raise ReconcileFencedError("leader Lease was lost") 

1135 # Direct method-level unit tests do not enter start(); production does. 

1136 if self._lease_name is None or self._lease_holder is None or self._leader_epoch is None: 

1137 return 

1138 coordination_v1 = client.CoordinationV1Api() 

1139 try: 

1140 lease = coordination_v1.read_namespaced_lease(self._lease_name, self.namespace) 

1141 except Exception as error: 

1142 self._leadership_lost.set() 

1143 raise ReconcileFencedError("leader Lease could not be verified") from error 

1144 if ( 

1145 lease.spec.holder_identity != self._lease_holder 

1146 or self._lease_annotations(lease).get(_LEADER_EPOCH_ANNOTATION) != self._leader_epoch 

1147 or self._lease_is_expired(lease, datetime.now(UTC)) 

1148 ): 

1149 self._leadership_lost.set() 

1150 raise ReconcileFencedError("leader Lease holder or epoch changed") 

1151 

1152 def stop(self) -> None: 

1153 """Stop the reconciliation loop and prevent additional mutations.""" 

1154 self._running = False 

1155 self._leadership_lost.set() 

1156 logger.info("Inference monitor stopped") 

1157 

1158 @staticmethod 

1159 def _lifecycle_metadata_complete(endpoint: dict[str, Any]) -> bool: 

1160 """Return whether an endpoint has complete immutable cleanup metadata.""" 

1161 lifecycle_id = endpoint.get("lifecycle_id") 

1162 cleanup_regions = endpoint.get("cleanup_regions") 

1163 region_generations = endpoint.get("region_generations") 

1164 return ( 

1165 isinstance(lifecycle_id, str) 

1166 and bool(lifecycle_id) 

1167 and isinstance(cleanup_regions, list) 

1168 and isinstance(region_generations, dict) 

1169 and all( 

1170 isinstance(region, str) 

1171 and bool(region) 

1172 and isinstance(region_generations.get(region), str) 

1173 and bool(region_generations[region]) 

1174 for region in cleanup_regions 

1175 ) 

1176 ) 

1177 

1178 def _status_write_conditions( 

1179 self, 

1180 endpoint: dict[str, Any], 

1181 *, 

1182 deleting: bool = False, 

1183 ) -> RegionStatusConditions: 

1184 """Return lifecycle/generation predicates for one regional status write.""" 

1185 lifecycle_id = endpoint.get("lifecycle_id") 

1186 if not isinstance(lifecycle_id, str) or not lifecycle_id: 

1187 return {} 

1188 conditions: RegionStatusConditions = {"expected_lifecycle_id": lifecycle_id} 

1189 if deleting: 

1190 generation = endpoint.get("deletion_generation") 

1191 if isinstance(generation, str) and generation: 

1192 conditions["expected_deletion_generation"] = generation 

1193 return conditions 

1194 raw_generations = endpoint.get("region_generations") 

1195 generations = raw_generations if isinstance(raw_generations, dict) else {} 

1196 region_generation = generations.get(self.region) 

1197 if isinstance(region_generation, str) and region_generation: 

1198 conditions["expected_region_generation"] = region_generation 

1199 return conditions 

1200 

1201 def _authority_from_endpoint(self, endpoint: dict[str, Any]) -> ReconcileAuthority | None: 

1202 """Build object-level provenance for a production endpoint snapshot.""" 

1203 lifecycle_id = endpoint.get("lifecycle_id") 

1204 generations = endpoint.get("region_generations") 

1205 region_generation = generations.get(self.region) if isinstance(generations, dict) else None 

1206 if not isinstance(lifecycle_id, str) or not lifecycle_id: 

1207 if isinstance(endpoint.get("updated_at"), str): 

1208 raise ReconcileFencedError("endpoint snapshot has no lifecycle authority") 

1209 return None # Compatibility for isolated method-level fixtures only. 

1210 if not isinstance(region_generation, str) or not region_generation: 

1211 if isinstance(endpoint.get("updated_at"), str): 

1212 raise ReconcileFencedError("endpoint snapshot has no Region generation") 

1213 return None 

1214 desired_state = endpoint.get("desired_state") 

1215 targets = endpoint.get("target_regions") 

1216 target_regions = targets if isinstance(targets, list) else [] 

1217 deletion_generation = endpoint.get("deletion_generation") 

1218 return ReconcileAuthority( 

1219 endpoint_name=str(endpoint.get("endpoint_name", "")), 

1220 lifecycle_id=lifecycle_id, 

1221 region_generation=region_generation, 

1222 leader_epoch=self._leader_epoch or f"direct-{lifecycle_id[:16]}", 

1223 deletion_generation=( 

1224 deletion_generation 

1225 if isinstance(deletion_generation, str) and deletion_generation 

1226 else None 

1227 ), 

1228 deleting=desired_state == "deleted", 

1229 region_removed=desired_state != "deleted" and self.region not in target_regions, 

1230 ) 

1231 

1232 def _strong_authority_matches(self, authority: ReconcileAuthority) -> bool: 

1233 """Re-read DynamoDB before claiming legacy/previous-epoch objects.""" 

1234 try: 

1235 latest = self.store.get_endpoint(authority.endpoint_name, consistent_read=True) 

1236 except AttributeError, TypeError: 

1237 return getattr(self, "_lease_name", None) is None 

1238 if not isinstance(latest, dict): 

1239 return getattr(self, "_lease_name", None) is None 

1240 if latest.get("lifecycle_id") != authority.lifecycle_id: 

1241 return False 

1242 if authority.deleting: 

1243 return ( 

1244 latest.get("desired_state") == "deleted" 

1245 and latest.get("deletion_generation") == authority.deletion_generation 

1246 ) 

1247 generations = latest.get("region_generations") 

1248 return ( 

1249 latest.get("desired_state") != "deleted" 

1250 and isinstance(generations, dict) 

1251 and generations.get(self.region) == authority.region_generation 

1252 ) 

1253 

1254 @staticmethod 

1255 def _object_metadata(resource: Any) -> tuple[Any, dict[str, str], str | None, str | None]: 

1256 """Return metadata, annotations, UID, and resourceVersion for typed/dict objects.""" 

1257 if isinstance(resource, dict): 

1258 metadata = resource.get("metadata") 

1259 metadata = metadata if isinstance(metadata, dict) else {} 

1260 annotations = metadata.get("annotations") 

1261 return ( 

1262 metadata, 

1263 dict(annotations) if isinstance(annotations, dict) else {}, 

1264 metadata.get("uid") if isinstance(metadata.get("uid"), str) else None, 

1265 ( 

1266 metadata.get("resourceVersion") 

1267 if isinstance(metadata.get("resourceVersion"), str) 

1268 else None 

1269 ), 

1270 ) 

1271 metadata = getattr(resource, "metadata", None) 

1272 annotations = getattr(metadata, "annotations", None) 

1273 uid = getattr(metadata, "uid", None) 

1274 resource_version = getattr(metadata, "resource_version", None) 

1275 return ( 

1276 metadata, 

1277 dict(annotations) if isinstance(annotations, dict) else {}, 

1278 uid if isinstance(uid, str) else None, 

1279 resource_version if isinstance(resource_version, str) else None, 

1280 ) 

1281 

1282 @staticmethod 

1283 def _object_labels(resource: Any) -> dict[str, str]: 

1284 if isinstance(resource, dict): 

1285 metadata = resource.get("metadata") 

1286 labels = metadata.get("labels") if isinstance(metadata, dict) else None 

1287 else: 

1288 metadata = getattr(resource, "metadata", None) 

1289 labels = getattr(metadata, "labels", None) 

1290 return dict(labels) if isinstance(labels, dict) else {} 

1291 

1292 @classmethod 

1293 def _has_monitor_provenance(cls, resource: Any) -> bool: 

1294 labels = cls._object_labels(resource) 

1295 return labels.get("project") == "gco" and labels.get("gco.io/type") == "inference" 

1296 

1297 def _handoff_stale_resource( 

1298 self, 

1299 resource: Any, 

1300 *, 

1301 kind: str, 

1302 resource_name: str, 

1303 delete_resource: Callable[..., Any] | None, 

1304 reason: str, 

1305 ) -> NoReturn: 

1306 """UID-delete a proven stale monitor object only for current DDB authority.""" 

1307 authority = getattr(self, "_active_authority", None) 

1308 if ( 

1309 authority is None 

1310 or delete_resource is None 

1311 or not self._has_monitor_provenance(resource) 

1312 or not self._strong_authority_matches(authority) 

1313 ): 

1314 raise ReconcileFencedError(f"{kind} {resource_name} {reason}") 

1315 self._assert_current_leadership() 

1316 try: 

1317 delete_resource( 

1318 body=self._delete_options_for( 

1319 resource, 

1320 kind=kind, 

1321 resource_name=resource_name, 

1322 ) 

1323 ) 

1324 except ApiException as error: 

1325 if error.status not in {404, 409}: 

1326 raise 

1327 raise ReconcileFencedError( 

1328 f"{kind} {resource_name} stale authority handoff deletion requested" 

1329 ) 

1330 

1331 def _authorize_resource( 

1332 self, 

1333 resource: Any, 

1334 *, 

1335 kind: str, 

1336 resource_name: str, 

1337 patch_metadata: Callable[..., Any] | None = None, 

1338 read_resource: Callable[[], Any] | None = None, 

1339 delete_resource: Callable[..., Any] | None = None, 

1340 allow_region_mismatch: bool = False, 

1341 ) -> Any: 

1342 """Verify lifecycle provenance and CAS-claim the current leader epoch.""" 

1343 authority = getattr(self, "_active_authority", None) 

1344 if authority is None: 

1345 return resource 

1346 self._assert_current_leadership() 

1347 metadata, annotations, _uid, resource_version = self._object_metadata(resource) 

1348 if getattr(self, "_lease_name", None) is None and resource_version is None: 

1349 # Historical method-level fixtures use metadata-less MagicMocks. 

1350 # Production reconciliation always has a Lease and real metadata. 

1351 return resource 

1352 observed_lifecycle = annotations.get(_LIFECYCLE_ANNOTATION) 

1353 if observed_lifecycle is None and not self._has_monitor_provenance(resource): 

1354 raise ReconcileFencedError(f"{kind} {resource_name} has ambiguous legacy ownership") 

1355 if observed_lifecycle not in (None, authority.lifecycle_id): 

1356 self._handoff_stale_resource( 

1357 resource, 

1358 kind=kind, 

1359 resource_name=resource_name, 

1360 delete_resource=delete_resource, 

1361 reason="belongs to another endpoint lifecycle", 

1362 ) 

1363 observed_region = annotations.get(_REGION_GENERATION_ANNOTATION) 

1364 if not allow_region_mismatch and observed_region not in (None, authority.region_generation): 

1365 self._handoff_stale_resource( 

1366 resource, 

1367 kind=kind, 

1368 resource_name=resource_name, 

1369 delete_resource=delete_resource, 

1370 reason="belongs to another Region generation", 

1371 ) 

1372 expected = authority.annotations 

1373 if all(annotations.get(key) == value for key, value in expected.items()): 

1374 return resource 

1375 if patch_metadata is None or read_resource is None: 

1376 raise ReconcileFencedError(f"{kind} {resource_name} lacks current immutable provenance") 

1377 if not isinstance(resource_version, str) or not resource_version: 

1378 raise ReconcileFencedError( 

1379 f"{kind} {resource_name} has no resourceVersion for authority claim" 

1380 ) 

1381 if not self._strong_authority_matches(authority): 

1382 raise ReconcileFencedError("endpoint authority changed before Kubernetes mutation") 

1383 self._assert_current_leadership() 

1384 merged = dict(annotations) 

1385 merged.update(expected) 

1386 try: 

1387 patch_metadata( 

1388 body={ 

1389 "metadata": { 

1390 "resourceVersion": resource_version, 

1391 "annotations": merged, 

1392 } 

1393 } 

1394 ) 

1395 except Exception as error: 

1396 raise ReconcileFencedError( 

1397 f"{kind} {resource_name} changed during authority claim" 

1398 ) from error 

1399 claimed = read_resource() 

1400 _metadata, claimed_annotations, _claimed_uid, _claimed_version = self._object_metadata( 

1401 claimed 

1402 ) 

1403 if not all(claimed_annotations.get(key) == value for key, value in expected.items()): 

1404 raise ReconcileFencedError( 

1405 f"{kind} {resource_name} authority claim could not be verified" 

1406 ) 

1407 return claimed 

1408 

1409 def _provenance_annotations(self) -> dict[str, str] | None: 

1410 authority = getattr(self, "_active_authority", None) 

1411 return dict(authority.annotations) if authority is not None else None 

1412 

1413 def _assert_mutation_authority(self) -> None: 

1414 authority = getattr(self, "_active_authority", None) 

1415 if authority is None: 

1416 return 

1417 self._assert_current_leadership() 

1418 if not self._strong_authority_matches(authority): 

1419 raise ReconcileFencedError("endpoint authority changed before Kubernetes mutation") 

1420 

1421 def _confirm_created_resource( 

1422 self, 

1423 *, 

1424 kind: str, 

1425 resource_name: str, 

1426 read_resource: Callable[[], Any], 

1427 delete_resource: Callable[..., Any], 

1428 ) -> Any: 

1429 """Compensate an exact just-created object if Lease/DDB authority changed.""" 

1430 authority = getattr(self, "_active_authority", None) 

1431 if authority is None: 

1432 return None 

1433 try: 

1434 created = read_resource() 

1435 except ApiException as error: 

1436 if getattr(self, "_lease_name", None) is None and error.status == 404: 

1437 return None 

1438 raise 

1439 _metadata, annotations, _uid, resource_version = self._object_metadata(created) 

1440 if getattr(self, "_lease_name", None) is None and resource_version is None: 

1441 return created 

1442 if not all(annotations.get(key) == value for key, value in authority.annotations.items()): 

1443 raise ReconcileFencedError(f"{kind} {resource_name} post-create provenance changed") 

1444 try: 

1445 self._assert_mutation_authority() 

1446 except ReconcileFencedError: 

1447 # This process created the exact annotated UID. Remove only that UID; 

1448 # a replacement racing into the name makes the precondition fail and 

1449 # is never touched. 

1450 try: 

1451 delete_resource( 

1452 body=self._delete_options_for( 

1453 created, 

1454 kind=kind, 

1455 resource_name=resource_name, 

1456 ) 

1457 ) 

1458 except ApiException as error: 

1459 if error.status not in {404, 409}: 

1460 logger.warning( 

1461 "Post-create compensation failed for %s/%s: status %s", 

1462 kind, 

1463 resource_name, 

1464 error.status, 

1465 ) 

1466 raise 

1467 return created 

1468 

1469 def _delete_options_for(self, resource: Any, *, kind: str, resource_name: str) -> Any: 

1470 """Build UID/resourceVersion delete preconditions for one authorized object.""" 

1471 _metadata, _annotations, uid, resource_version = self._object_metadata(resource) 

1472 if uid and resource_version: 

1473 return client.V1DeleteOptions( 

1474 propagation_policy="Foreground", 

1475 preconditions=client.V1Preconditions( 

1476 uid=uid, 

1477 resource_version=resource_version, 

1478 ), 

1479 ) 

1480 if ( 

1481 getattr(self, "_active_authority", None) is None 

1482 or getattr(self, "_lease_name", None) is None 

1483 ): 

1484 return client.V1DeleteOptions( 

1485 propagation_policy="Foreground", 

1486 preconditions=client.V1Preconditions(uid=uid) if uid else None, 

1487 ) 

1488 raise ReconcileFencedError( 

1489 f"{kind} {resource_name} lacks UID/resourceVersion delete authority" 

1490 ) 

1491 

1492 async def reconcile(self) -> list[dict[str, Any]]: 

1493 """Run one reconciliation cycle with generation-fenced deletion.""" 

1494 self._reconcile_count += 1 

1495 actions: list[dict[str, Any]] = [] 

1496 try: 

1497 endpoints = self.store.list_endpoints() 

1498 except Exception as e: 

1499 logger.error("Failed to list endpoints from DynamoDB: %s", e) 

1500 return actions 

1501 

1502 # Records written before lifecycle fencing are upgraded from their 

1503 # DynamoDB snapshot before any Kubernetes mutation. The update is 

1504 # conditional on ``updated_at``; a concurrent writer wins and this pass 

1505 # simply retries from the next scan. Direct method-level test fixtures 

1506 # without a persistence timestamp remain outside this production path. 

1507 normalized_endpoints: list[dict[str, Any]] = [] 

1508 for endpoint in endpoints: 

1509 if not self._lifecycle_metadata_complete(endpoint) and isinstance( 

1510 endpoint.get("updated_at"), str 

1511 ): 

1512 upgraded = self.store.ensure_lifecycle_metadata(endpoint) 

1513 if not isinstance(upgraded, dict): 

1514 continue 

1515 endpoint = upgraded 

1516 actions.append( 

1517 { 

1518 "action": "initialize_lifecycle", 

1519 "endpoint": endpoint.get("endpoint_name", "unknown"), 

1520 } 

1521 ) 

1522 normalized_endpoints.append(endpoint) 

1523 endpoints = normalized_endpoints 

1524 

1525 for endpoint in endpoints: 

1526 name = endpoint.get("endpoint_name", "unknown") 

1527 try: 

1528 action = await self._reconcile_endpoint(endpoint) 

1529 if action: 

1530 actions.append(action) 

1531 except ReconcileFencedError as error: 

1532 # Authority loss is not endpoint health. Another leader may 

1533 # already have written the terminal acknowledgement; a stale 

1534 # error write must never regress that quorum to ``error``. 

1535 logger.warning("Stopped stale reconciliation for %s: %s", name, error) 

1536 continue 

1537 except Exception as e: 

1538 logger.error("Failed to reconcile endpoint %s: %s", name, e) 

1539 self._errors_count += 1 

1540 status_conditions = self._status_write_conditions( 

1541 endpoint, 

1542 deleting=endpoint.get("desired_state") == "deleted", 

1543 ) 

1544 self.store.update_region_status( 

1545 name, 

1546 self.region, 

1547 "error", 

1548 error=str(e), 

1549 **status_conditions, 

1550 ) 

1551 

1552 # Any monitor may purge, but only from a strong snapshot containing a 

1553 # fresh terminal acknowledgement for every immutable deletion member. 

1554 # Each acknowledgement itself represents two child-complete inventory 

1555 # sweeps, and the delete is conditioned on the same lifecycle, 

1556 # generation, and updated_at snapshot. 

1557 for endpoint in endpoints: 

1558 if endpoint.get("desired_state") != "deleted": 

1559 continue 

1560 ep_name = endpoint.get("endpoint_name") 

1561 if not isinstance(ep_name, str): 

1562 continue 

1563 try: 

1564 latest = self.store.get_endpoint(ep_name, consistent_read=True) 

1565 except Exception as e: 

1566 logger.warning("Failed to refresh deleted endpoint %s: %s", ep_name, e) 

1567 continue 

1568 if not isinstance(latest, dict) or latest.get("desired_state") != "deleted": 

1569 continue 

1570 lifecycle_id = latest.get("lifecycle_id") 

1571 generation = latest.get("deletion_generation") 

1572 deletion_regions = latest.get("deletion_regions") 

1573 updated_at = latest.get("updated_at") 

1574 if not all( 

1575 isinstance(value, str) and value for value in (lifecycle_id, generation, updated_at) 

1576 ): 

1577 continue 

1578 if not isinstance(deletion_regions, list) or not deletion_regions: 

1579 continue 

1580 cleanup_regions = { 

1581 region for region in deletion_regions if isinstance(region, str) and region 

1582 } 

1583 if len(cleanup_regions) != len(deletion_regions): 

1584 continue 

1585 raw_status = latest.get("region_status") 

1586 region_status = raw_status if isinstance(raw_status, dict) else {} 

1587 if not all( 

1588 isinstance(region_status.get(region), dict) 

1589 and region_status[region].get("state") == "deleted" 

1590 and region_status[region].get("lifecycle_id") == lifecycle_id 

1591 and region_status[region].get("deletion_generation") == generation 

1592 and region_status[region].get("absence_observations", 0) >= 2 

1593 for region in cleanup_regions 

1594 ): 

1595 continue 

1596 try: 

1597 deleted = self.store.delete_endpoint( 

1598 ep_name, 

1599 expected_updated_at=updated_at, 

1600 expected_lifecycle_id=lifecycle_id, 

1601 expected_deletion_generation=generation, 

1602 ) 

1603 if not deleted: 

1604 continue 

1605 logger.info( 

1606 "Purged endpoint %s lifecycle %s generation %s", 

1607 ep_name, 

1608 lifecycle_id, 

1609 generation, 

1610 ) 

1611 actions.append({"action": "purge", "endpoint": ep_name}) 

1612 except Exception as e: 

1613 logger.warning("Failed to purge endpoint %s: %s", ep_name, e) 

1614 return actions 

1615 

1616 @staticmethod 

1617 def _cleanup_ack_is_terminal(endpoint: dict[str, Any], region: str) -> bool: 

1618 """Return whether this region already durably acknowledged this generation.""" 

1619 raw_statuses = endpoint.get("region_status") 

1620 statuses = raw_statuses if isinstance(raw_statuses, dict) else {} 

1621 status = statuses.get(region) 

1622 if not isinstance(status, dict) or status.get("state") != "deleted": 

1623 return False 

1624 lifecycle_id = endpoint.get("lifecycle_id") 

1625 if status.get("lifecycle_id") != lifecycle_id: 

1626 return False 

1627 if endpoint.get("desired_state") == "deleted": 

1628 return ( 

1629 status.get("deletion_generation") == endpoint.get("deletion_generation") 

1630 and status.get("absence_observations", 0) >= 2 

1631 ) 

1632 raw_generations = endpoint.get("region_generations") 

1633 generations = raw_generations if isinstance(raw_generations, dict) else {} 

1634 return ( 

1635 status.get("region_generation") == generations.get(region) 

1636 and status.get("absence_observations", 0) >= 2 

1637 ) 

1638 

1639 def _record_cleanup_observation( 

1640 self, 

1641 endpoint: dict[str, Any], 

1642 cleanup: ResourceCleanupResult, 

1643 ) -> tuple[str, bool]: 

1644 """Persist one stable-absence observation for the active lifecycle/generation.""" 

1645 lifecycle_id = endpoint.get("lifecycle_id") 

1646 if not isinstance(lifecycle_id, str) or not lifecycle_id: 

1647 raise RuntimeError("Endpoint cleanup requires an immutable lifecycle id") 

1648 deleting = endpoint.get("desired_state") == "deleted" 

1649 generation = endpoint.get("deletion_generation") if deleting else None 

1650 raw_region_generations = endpoint.get("region_generations") 

1651 region_generations = ( 

1652 raw_region_generations if isinstance(raw_region_generations, dict) else {} 

1653 ) 

1654 region_generation = region_generations.get(self.region) if not deleting else None 

1655 if deleting and (not isinstance(generation, str) or not generation): 

1656 raise RuntimeError("Endpoint deletion requires an immutable deletion generation") 

1657 if not deleting and (not isinstance(region_generation, str) or not region_generation): 

1658 raise RuntimeError("Endpoint cleanup requires a current Region generation") 

1659 

1660 raw_statuses = endpoint.get("region_status") 

1661 statuses = raw_statuses if isinstance(raw_statuses, dict) else {} 

1662 previous = statuses.get(self.region) 

1663 same_generation = ( 

1664 isinstance(previous, dict) 

1665 and previous.get("lifecycle_id") == lifecycle_id 

1666 and ( 

1667 previous.get("deletion_generation") == generation 

1668 if deleting 

1669 else previous.get("region_generation") == region_generation 

1670 ) 

1671 ) 

1672 previous_observations = ( 

1673 int(previous.get("absence_observations", 0)) 

1674 if same_generation and isinstance(previous, dict) 

1675 else 0 

1676 ) 

1677 observations = previous_observations + 1 if cleanup.complete else 0 

1678 state = "deleted" if observations >= 2 else "deleting" 

1679 status_kwargs: dict[str, Any] = { 

1680 "extra": {"absence_observations": observations}, 

1681 "expected_lifecycle_id": lifecycle_id, 

1682 } 

1683 if deleting: 

1684 status_kwargs["expected_deletion_generation"] = generation 

1685 else: 

1686 status_kwargs["expected_region_generation"] = region_generation 

1687 if cleanup.error_message: 

1688 status_kwargs["error"] = cleanup.error_message 

1689 written = self.store.update_region_status( 

1690 endpoint["endpoint_name"], 

1691 self.region, 

1692 state, 

1693 **status_kwargs, 

1694 ) 

1695 return state, bool(written) 

1696 

1697 async def _reconcile_endpoint(self, endpoint: dict[str, Any]) -> dict[str, Any] | None: 

1698 """Reconcile one endpoint under immutable Kubernetes provenance.""" 

1699 authority = self._authority_from_endpoint(endpoint) 

1700 previous = self._active_authority 

1701 self._active_authority = authority 

1702 try: 

1703 return await self._reconcile_endpoint_authorized(endpoint) 

1704 finally: 

1705 self._active_authority = previous 

1706 

1707 async def _reconcile_endpoint_authorized( 

1708 self, endpoint: dict[str, Any] 

1709 ) -> dict[str, Any] | None: 

1710 """Implementation for one endpoint after authority has been installed.""" 

1711 name = endpoint["endpoint_name"] 

1712 desired_state = endpoint.get("desired_state", "deploying") 

1713 target_regions = endpoint.get("target_regions", []) 

1714 spec = endpoint.get("spec", {}) 

1715 ns = endpoint.get("namespace", self.namespace) 

1716 

1717 lifecycle_value = endpoint.get("lifecycle_id") 

1718 lifecycle_id = ( 

1719 lifecycle_value if isinstance(lifecycle_value, str) and lifecycle_value else None 

1720 ) 

1721 

1722 if desired_state == "deleted": 

1723 generation = endpoint.get("deletion_generation") 

1724 deletion_regions = endpoint.get("deletion_regions") 

1725 if not isinstance(generation, str) or not isinstance(deletion_regions, list): 

1726 # Upgrade a legacy or interrupted delete transition atomically; 

1727 # the next pass sees the persisted immutable snapshot. 

1728 self.store.update_desired_state( 

1729 name, 

1730 "deleted", 

1731 expected_lifecycle_id=lifecycle_id, 

1732 ) 

1733 return {"action": "initialize_deletion", "endpoint": name} 

1734 if self.region not in deletion_regions: 

1735 return None 

1736 if self._cleanup_ack_is_terminal(endpoint, self.region): 

1737 # Durable current-generation acknowledgement makes completed 

1738 # non-target work quiescent: no Kubernetes reads and no DDB write. 

1739 return None 

1740 return self._reconcile_deleted(endpoint, ns, spec if isinstance(spec, dict) else None) 

1741 

1742 if self.region not in target_regions: 

1743 cleanup_regions = endpoint.get("cleanup_regions") 

1744 if not isinstance(cleanup_regions, list) or self.region not in cleanup_regions: 

1745 return None 

1746 if self._cleanup_ack_is_terminal(endpoint, self.region): 

1747 return None 

1748 cleanup = self._delete_resources( 

1749 name, 

1750 ns, 

1751 spec if isinstance(spec, dict) else None, 

1752 expected_lifecycle_id=lifecycle_id, 

1753 ) 

1754 state, written = self._record_cleanup_observation(endpoint, cleanup) 

1755 return { 

1756 "action": "cleanup", 

1757 "endpoint": name, 

1758 "reason": "region_removed", 

1759 "cleanup_complete": state == "deleted" and written, 

1760 } 

1761 

1762 if desired_state in ("deploying", "running"): 

1763 if not isinstance(spec, dict): 

1764 error = "endpoint spec must be a mapping" 

1765 elif "mooncake" in spec and "canary" in spec: 

1766 error = "endpoint spec cannot combine 'mooncake' and 'canary' blocks" 

1767 else: 

1768 return await self._reconcile_running(name, ns, spec, endpoint) 

1769 logger.error("Rejecting invalid endpoint %s: %s", name, error) 

1770 self.store.update_region_status( 

1771 name, 

1772 self.region, 

1773 "failed", 

1774 error=error, 

1775 **self._status_write_conditions(endpoint), 

1776 ) 

1777 return {"action": "reject", "endpoint": name, "reason": "invalid_spec"} 

1778 if desired_state == "stopped": 

1779 return self._reconcile_stopped( 

1780 name, 

1781 ns, 

1782 spec if isinstance(spec, dict) else None, 

1783 endpoint, 

1784 ) 

1785 return None 

1786 

1787 async def _reconcile_running( 

1788 self, 

1789 name: str, 

1790 namespace: str, 

1791 spec: dict[str, Any], 

1792 endpoint: dict[str, Any], 

1793 ) -> dict[str, Any] | None: 

1794 """Ensure the endpoint is running with the correct spec.""" 

1795 status_conditions = self._status_write_conditions(endpoint) 

1796 # Specs carrying a ``mooncake`` block take the disaggregated path. The 

1797 # branch returns ``None`` when no such block is present, so a plain 

1798 # endpoint falls through to the single-Deployment path below unchanged. 

1799 mooncake_action = await self._reconcile_mooncake(name, namespace, spec, endpoint) 

1800 if mooncake_action is not None: 

1801 return mooncake_action 

1802 

1803 deployment = self._get_deployment(name, namespace) 

1804 configured_replicas = spec.get("replicas", 1) 

1805 raw_autoscaling = spec.get("autoscaling", {}) 

1806 autoscaling = raw_autoscaling if isinstance(raw_autoscaling, dict) else {} 

1807 autoscaling_enabled = bool(autoscaling.get("enabled")) 

1808 

1809 if deployment is None: 

1810 # A missing Deployment is still an ownership handoff boundary. Do 

1811 # not recreate it until every obsolete HPA/KEDA owner is absent. 

1812 if autoscaling_enabled: 

1813 ownership = self._reconcile_classic_autoscaler( 

1814 name, 

1815 namespace, 

1816 spec, 

1817 apply_desired=False, 

1818 ) 

1819 else: 

1820 ownership = self._delete_autoscalers( 

1821 (name,), 

1822 (name, f"keda-hpa-{name}"), 

1823 namespace, 

1824 ) 

1825 if not ownership.complete: 

1826 ownership_status_kwargs: dict[str, Any] = {} 

1827 if ownership.error_message: 

1828 ownership_status_kwargs["error"] = ownership.error_message 

1829 self.store.update_region_status( 

1830 name, 

1831 self.region, 

1832 "updating", 

1833 replicas_ready=0, 

1834 replicas_desired=0, 

1835 **status_conditions, 

1836 **ownership_status_kwargs, 

1837 ) 

1838 return { 

1839 "action": "reconcile_autoscaler", 

1840 "endpoint": name, 

1841 "cleanup_complete": False, 

1842 } 

1843 

1844 logger.info("Creating endpoint %s in %s", name, self.region) 

1845 self._create_deployment(name, namespace, spec) 

1846 self._create_service(name, namespace, spec) 

1847 if autoscaling_enabled: 

1848 self._create_or_update_hpa(name, namespace, spec) 

1849 self.store.update_region_status( 

1850 name, 

1851 self.region, 

1852 "creating", 

1853 replicas_desired=( 

1854 int(autoscaling.get("min_replicas", 1)) 

1855 if autoscaling_enabled 

1856 else configured_replicas 

1857 ), 

1858 **status_conditions, 

1859 ) 

1860 return {"action": "create", "endpoint": name} 

1861 

1862 # Deployment exists — ensure its Service exists. Public traffic follows 

1863 # ``gco-system/gco-gateway``'s shared ``/inference`` HTTPRoute to 

1864 # ``gco-system/inference-proxy``, which then reaches this endpoint's 

1865 # ClusterIP Service. 

1866 self._ensure_service(name, namespace, spec) 

1867 

1868 # Once enabled, HPA/KEDA is the sole owner of Deployment 

1869 # ``spec.replicas``; the static endpoint count must never fight it. 

1870 # Controller creation/handoff runs after capturing live replica status 

1871 # below so a pending ownership transition can report useful progress. 

1872 

1873 observed_desired = getattr(deployment.spec, "replicas", None) 

1874 live_desired_replicas = ( 

1875 int(observed_desired) if observed_desired is not None else configured_replicas 

1876 ) 

1877 current_replicas = live_desired_replicas 

1878 ready_replicas = int(getattr(deployment.status, "ready_replicas", 0) or 0) 

1879 # During enabled -> disabled handoff the old owner still controls this 

1880 # observed value. Report it until static reconciliation actually takes 

1881 # ownership, rather than publishing an aspirational configured count. 

1882 status_desired_replicas = live_desired_replicas 

1883 readiness_floor = ( 

1884 int(autoscaling.get("min_replicas", 1)) if autoscaling_enabled else configured_replicas 

1885 ) 

1886 

1887 self._check_health_watchdog( 

1888 name, 

1889 namespace, 

1890 ready_replicas, 

1891 status_desired_replicas, 

1892 spec, 

1893 endpoint, 

1894 ) 

1895 

1896 autoscaler_cleanup: ResourceCleanupResult 

1897 if autoscaling_enabled: 

1898 # Reconcile on every existing-Deployment pass to repair partial 

1899 # creation and configuration drift. Ownership handoffs remain 

1900 # updating until the obsolete controller is actually absent. 

1901 autoscaler_cleanup = self._reconcile_classic_autoscaler(name, namespace, spec) 

1902 else: 

1903 # Static ownership is explicit even when the autoscaling block was 

1904 # removed entirely: stale HPA/KEDA objects must never regain count. 

1905 autoscaler_cleanup = self._delete_autoscalers( 

1906 (name,), 

1907 (name, f"keda-hpa-{name}"), 

1908 namespace, 

1909 ) 

1910 

1911 if not autoscaler_cleanup.complete: 

1912 status_kwargs: dict[str, Any] = {} 

1913 if autoscaler_cleanup.error_message: 

1914 status_kwargs["error"] = autoscaler_cleanup.error_message 

1915 self.store.update_region_status( 

1916 name, 

1917 self.region, 

1918 "updating", 

1919 replicas_ready=ready_replicas, 

1920 replicas_desired=status_desired_replicas, 

1921 **status_conditions, 

1922 **status_kwargs, 

1923 ) 

1924 return { 

1925 "action": "reconcile_autoscaler", 

1926 "endpoint": name, 

1927 "cleanup_complete": False, 

1928 } 

1929 

1930 if not autoscaling_enabled and current_replicas != configured_replicas: 

1931 logger.info( 

1932 "Scaling endpoint %s: %d → %d replicas", 

1933 name, 

1934 current_replicas, 

1935 configured_replicas, 

1936 ) 

1937 self._scale_deployment(name, namespace, configured_replicas) 

1938 self.store.update_region_status( 

1939 name, 

1940 self.region, 

1941 "updating", 

1942 replicas_ready=ready_replicas, 

1943 replicas_desired=configured_replicas, 

1944 **status_conditions, 

1945 ) 

1946 return {"action": "scale", "endpoint": name, "replicas": configured_replicas} 

1947 

1948 # Check if image changed 

1949 current_image = self._get_deployment_image(deployment) 

1950 desired_image = self._resolve_image_for_region(spec) if spec.get("image") else "" 

1951 if current_image and desired_image and current_image != desired_image: 

1952 logger.info("Updating endpoint %s image: %s → %s", name, current_image, desired_image) 

1953 self._update_deployment_image(name, namespace, desired_image) 

1954 self.store.update_region_status( 

1955 name, 

1956 self.region, 

1957 "updating", 

1958 replicas_ready=ready_replicas, 

1959 replicas_desired=status_desired_replicas, 

1960 **status_conditions, 

1961 ) 

1962 return {"action": "update_image", "endpoint": name, "image": desired_image} 

1963 

1964 # Reconcile canary first and publish only observed readiness. The 

1965 # authenticated proxy will not sample canary traffic until this exact 

1966 # region reports the matching image fully Ready. 

1967 canary = spec.get("canary") 

1968 canary_status = None 

1969 if isinstance(canary, dict): 

1970 canary_status = self._reconcile_canary(name, namespace, spec, canary, endpoint) 

1971 else: 

1972 self._cleanup_canary(name, namespace) 

1973 

1974 # For an autoscaled endpoint, readiness means the configured minimum 

1975 # serving capacity is available. During ordinary scale-out the live HPA 

1976 # target can temporarily exceed Ready pods without making a healthy 

1977 # endpoint flap back to "creating". Keep reporting that live target so 

1978 # status and watchdog diagnostics remain truthful. 

1979 state = "running" if ready_replicas >= readiness_floor else "creating" 

1980 self.store.update_region_status( 

1981 name, 

1982 self.region, 

1983 state, 

1984 replicas_ready=ready_replicas, 

1985 replicas_desired=status_desired_replicas, 

1986 extra={"canary": canary_status} if canary_status is not None else None, 

1987 **status_conditions, 

1988 ) 

1989 

1990 # Promote desired state only from live local readiness plus explicit 

1991 # running observations for every *other* target region. The endpoint 

1992 # object may contain a stale local region_status from before this pass. 

1993 if state == "running" and endpoint.get("desired_state") == "deploying": 

1994 stored_statuses = endpoint.get("region_status", {}) 

1995 target_regions = endpoint.get("target_regions", []) 

1996 all_running = bool(target_regions) 

1997 for target_region in target_regions: 

1998 if target_region == self.region: 

1999 continue 

2000 target_status = ( 

2001 stored_statuses.get(target_region, {}) 

2002 if isinstance(stored_statuses, dict) 

2003 else {} 

2004 ) 

2005 if not isinstance(target_status, dict) or target_status.get("state") != "running": 

2006 all_running = False 

2007 break 

2008 if all_running: 

2009 lifecycle_id = endpoint.get("lifecycle_id") 

2010 if isinstance(lifecycle_id, str) and lifecycle_id: 

2011 self.store.update_desired_state( 

2012 name, 

2013 "running", 

2014 expected_lifecycle_id=lifecycle_id, 

2015 expected_desired_state="deploying", 

2016 ) 

2017 

2018 return None 

2019 

2020 # ------------------------------------------------------------------ 

2021 # Mooncake reconciliation branch 

2022 # ------------------------------------------------------------------ 

2023 

2024 @staticmethod 

2025 def _desired_roles(mode: str | None) -> list[str]: 

2026 """Return the worker roles a mode materializes, in a stable order. 

2027 

2028 Disaggregated and ``both`` modes split work across ``prefill`` then 

2029 ``decode``; store mode runs a single ``kv_both`` instance under the 

2030 ``single`` role. The order is fixed so role creation and status 

2031 reporting are deterministic across passes. 

2032 """ 

2033 roles = _WORKER_ROLES_BY_MODE.get(mode, set()) if isinstance(mode, str) else set() 

2034 return [role for role in ("prefill", "decode", "single") if role in roles] 

2035 

2036 @staticmethod 

2037 def _needs_shared_master(mooncake: dict[str, Any]) -> bool: 

2038 """Whether the endpoint depends on the shared per-region master. 

2039 

2040 The store-bearing modes (``store`` and ``both``) always reach the 

2041 master for KV metadata, and any endpoint transferring over RDMA reaches 

2042 the master's built-in metadata server for the connector handshake. A 

2043 disaggregated endpoint transferring over TCP needs no master. 

2044 """ 

2045 mode = mooncake.get("mode") 

2046 if mode in ("store", "both"): 

2047 return True 

2048 transfer = mooncake.get("transfer") or {} 

2049 return bool(transfer.get("protocol", "rdma") == "rdma") 

2050 

2051 def _ensure_mooncake_configmap(self, name: str, ns: str, cfg: dict[str, Any]) -> None: 

2052 """Create or update the shared transport ConfigMap for an endpoint. 

2053 

2054 The rendered transport settings (the dict produced by 

2055 :func:`render_mooncake_config`) are written to a ConfigMap named 

2056 ``{name}-mooncake`` under the ``mooncake.json`` key, which each role pod 

2057 mounts at the configured path. Creation is idempotent: an existing 

2058 ConfigMap is patched to the desired contents so a transport change on 

2059 the spec propagates on the next pass. 

2060 """ 

2061 cm_name = f"{name}-mooncake" 

2062 config_map = client.V1ConfigMap( 

2063 metadata=client.V1ObjectMeta( 

2064 name=cm_name, 

2065 namespace=ns, 

2066 labels={"app": name, "project": "gco", "gco.io/type": "inference"}, 

2067 annotations=self._provenance_annotations(), 

2068 ), 

2069 data={"mooncake.json": json.dumps(cfg, sort_keys=True)}, 

2070 ) 

2071 self._assert_mutation_authority() 

2072 try: 

2073 self.core_v1.create_namespaced_config_map( 

2074 ns, config_map, _request_timeout=self._k8s_timeout 

2075 ) 

2076 self._confirm_created_resource( 

2077 kind="configmap", 

2078 resource_name=cm_name, 

2079 read_resource=partial( 

2080 self.core_v1.read_namespaced_config_map, 

2081 cm_name, 

2082 ns, 

2083 _request_timeout=self._k8s_timeout, 

2084 ), 

2085 delete_resource=partial( 

2086 self.core_v1.delete_namespaced_config_map, 

2087 cm_name, 

2088 ns, 

2089 _request_timeout=self._k8s_timeout, 

2090 ), 

2091 ) 

2092 logger.info("Created mooncake config map %s/%s", ns, cm_name) 

2093 except ApiException as error: 

2094 if error.status != 409: 

2095 raise 

2096 existing = self.core_v1.read_namespaced_config_map( 

2097 cm_name, ns, _request_timeout=self._k8s_timeout 

2098 ) 

2099 existing = self._authorize_resource( 

2100 existing, 

2101 kind="configmap", 

2102 resource_name=cm_name, 

2103 patch_metadata=partial( 

2104 self.core_v1.patch_namespaced_config_map, 

2105 cm_name, 

2106 ns, 

2107 _request_timeout=self._k8s_timeout, 

2108 ), 

2109 read_resource=lambda: self.core_v1.read_namespaced_config_map( 

2110 cm_name, ns, _request_timeout=self._k8s_timeout 

2111 ), 

2112 delete_resource=partial( 

2113 self.core_v1.delete_namespaced_config_map, 

2114 cm_name, 

2115 ns, 

2116 _request_timeout=self._k8s_timeout, 

2117 ), 

2118 ) 

2119 _metadata, _annotations, _uid, resource_version = self._object_metadata(existing) 

2120 config_map.metadata.resource_version = resource_version 

2121 self._assert_mutation_authority() 

2122 self.core_v1.patch_namespaced_config_map( 

2123 cm_name, ns, config_map, _request_timeout=self._k8s_timeout 

2124 ) 

2125 logger.info("Updated mooncake config map %s/%s", ns, cm_name) 

2126 

2127 def _ensure_role_deployment( 

2128 self, name: str, ns: str, spec: dict[str, Any], role: str 

2129 ) -> tuple[int, int, bool]: 

2130 """Ensure one role target has its static or restart-seed capacity. 

2131 

2132 Existing autoscaled targets normally retain controller ownership, but a 

2133 manually stopped zero target is seeded to the role minimum before its 

2134 autoscaler is reapplied. Static roles always converge to topology. 

2135 """ 

2136 mooncake = spec.get("mooncake") or {} 

2137 deploy_name = name if role == "single" else f"{name}-{role}" 

2138 desired = self._replica_count_for_role(mooncake, role) 

2139 

2140 deployment = self._get_deployment(deploy_name, ns) 

2141 if deployment is None: 

2142 self._create_role_deployment(name, ns, spec, role) 

2143 return 0, desired, False 

2144 

2145 autoscaling = mooncake.get("autoscaling") or {} 

2146 role_autoscaling = autoscaling.get(role) 

2147 autoscaled = ( 

2148 bool(autoscaling.get("enabled")) 

2149 and role in ("prefill", "decode") 

2150 and isinstance(role_autoscaling, dict) 

2151 ) 

2152 current = deployment.spec.replicas or 0 

2153 restarted = autoscaled and current == 0 

2154 if (not autoscaled and current != desired) or restarted: 

2155 logger.info( 

2156 "Scaling role deployment %s/%s: %d → %d", 

2157 ns, 

2158 deploy_name, 

2159 current, 

2160 desired, 

2161 ) 

2162 self._scale_deployment(deploy_name, ns, desired) 

2163 

2164 status = getattr(deployment, "status", None) 

2165 ready = int(getattr(status, "ready_replicas", 0) or 0) if status else 0 

2166 return ready, desired, restarted 

2167 

2168 def _report_role_status( 

2169 self, 

2170 name: str, 

2171 ns: str, 

2172 mooncake: dict[str, Any], 

2173 region_services: dict[str, Any], 

2174 endpoint: dict[str, Any] | None = None, 

2175 ) -> str: 

2176 """Write the role-keyed region status for a Mooncake endpoint. 

2177 

2178 For split topologies the status carries a ``roles`` map of observed and 

2179 desired replica counts per role; for store-bearing endpoints it carries 

2180 a ``store`` sub-status with the master's readiness and address. The flat 

2181 ``replicas_ready`` / ``replicas_desired`` fields are also populated with 

2182 the totals so consumers that only read the flat shape still see motion. 

2183 

2184 Returns: 

2185 The reported endpoint state: ``"running"`` once every desired role 

2186 replica (and, when applicable, the master) is Ready, otherwise 

2187 ``"creating"``. 

2188 """ 

2189 mode = mooncake.get("mode") 

2190 roles = self._desired_roles(mode) 

2191 extra: dict[str, Any] = {} 

2192 total_ready = 0 

2193 total_desired = 0 

2194 all_ready = True 

2195 

2196 if mode in ("disaggregated", "both"): 

2197 roles_block: dict[str, Any] = {} 

2198 for role in ("prefill", "decode"): 

2199 if role not in roles: 

2200 continue 

2201 deploy_name = f"{name}-{role}" 

2202 configured_floor = self._replica_count_for_role(mooncake, role) 

2203 dep = self._get_deployment(deploy_name, ns) 

2204 status = getattr(dep, "status", None) if dep else None 

2205 ready = int(getattr(status, "ready_replicas", 0) or 0) if status else 0 

2206 deployment_spec = getattr(dep, "spec", None) if dep else None 

2207 observed_desired = getattr(deployment_spec, "replicas", None) 

2208 desired = ( 

2209 int(observed_desired) if observed_desired is not None else configured_floor 

2210 ) 

2211 roles_block[role] = {"ready": ready, "desired": desired} 

2212 total_ready += ready 

2213 total_desired += desired 

2214 if ready < configured_floor: 

2215 all_ready = False 

2216 extra["roles"] = roles_block 

2217 else: 

2218 # Store mode runs a single kv_both Deployment under the endpoint name. 

2219 configured_floor = self._replica_count_for_role(mooncake, "single") 

2220 dep = self._get_deployment(name, ns) 

2221 status = getattr(dep, "status", None) if dep else None 

2222 ready = int(getattr(status, "ready_replicas", 0) or 0) if status else 0 

2223 deployment_spec = getattr(dep, "spec", None) if dep else None 

2224 observed_desired = getattr(deployment_spec, "replicas", None) 

2225 desired = int(observed_desired) if observed_desired is not None else configured_floor 

2226 total_ready += ready 

2227 total_desired += desired 

2228 if ready < configured_floor: 

2229 all_ready = False 

2230 

2231 store = mooncake.get("store") or {} 

2232 if store.get("enabled"): 

2233 master_ready = self._mooncake_master_ready_replicas(ns) >= 1 

2234 extra["store"] = { 

2235 "ready": master_ready, 

2236 "master": region_services.get("master_server_address"), 

2237 } 

2238 if not master_ready: 

2239 all_ready = False 

2240 

2241 state = "running" if all_ready and total_desired > 0 else "creating" 

2242 status_conditions = self._status_write_conditions(endpoint or {}) 

2243 self.store.update_region_status( 

2244 name, 

2245 self.region, 

2246 state, 

2247 replicas_ready=total_ready, 

2248 replicas_desired=total_desired, 

2249 extra=extra or None, 

2250 **status_conditions, 

2251 ) 

2252 return state 

2253 

2254 async def _reconcile_mooncake( 

2255 self, 

2256 name: str, 

2257 ns: str, 

2258 spec: dict[str, Any], 

2259 endpoint: dict[str, Any], 

2260 ) -> dict[str, Any] | None: 

2261 """Reconcile an endpoint whose spec carries a ``mooncake`` block. 

2262 

2263 Returns ``None`` when the spec carries no ``mooncake`` block, signalling 

2264 the caller to take the single-Deployment path: one Deployment at the 

2265 configured replica count and one internal ClusterIP Service, with no role 

2266 split, proxy, autoscaler, shared-master dependency, endpoint Ingress, 

2267 Gateway, or HTTPRoute. The shared platform route remains unchanged. 

2268 

2269 With a ``mooncake`` block present, the topology is materialized in 

2270 dependency order, and the shared ConfigMap and master are laid down 

2271 before any role pod, the roles before the front-end, and the front-end 

2272 before status is written: 

2273 

2274 1. Resolve the in-region addresses (master, metadata, optional cold 

2275 tier). When the store is enabled but no own-region master is 

2276 configured, nothing further is materialized; the existing 

2277 configuration is left unchanged and the endpoint is reported as 

2278 still coming up with the unresolved-master reason. 

2279 2. Confirm every wired address stays inside the monitor's own region. A 

2280 cross-region address fails the endpoint and materializes nothing, 

2281 leaving any prior resources in place. 

2282 3. Gate dependent pods on the shared per-region master, which also lays 

2283 down the intra-namespace allow rules. While the master is not Ready, 

2284 or if it could not be created, nothing further is materialized and 

2285 the endpoint is reported as still coming up. 

2286 4. Render and apply the shared transport ConfigMap. 

2287 5. Materialize each role Deployment: prefill and decode for 

2288 disaggregated and both modes, a single ``kv_both`` Deployment for 

2289 store mode. 

2290 6. Materialize each present role's autoscaler when autoscaling is on. 

2291 7. Front disaggregated and both modes with the proxy and its internal 

2292 ClusterIP Service; give store mode an internal ClusterIP Service. 

2293 Public traffic remains on the shared ``gco-system/gco-gateway`` 

2294 HTTPRoute from ``/inference`` to ``gco-system/inference-proxy``. 

2295 8. Write the role-keyed region status. 

2296 

2297 Returns: 

2298 An action record describing what the pass did, or ``None`` when the 

2299 spec carries no ``mooncake`` block. 

2300 """ 

2301 mooncake = spec.get("mooncake") 

2302 if not mooncake: 

2303 return None 

2304 

2305 status_conditions = self._status_write_conditions(endpoint) 

2306 mode = mooncake.get("mode") 

2307 

2308 # Step 1: resolve in-region addresses. A store without an own-region 

2309 # master is left untouched and reported as still coming up. 

2310 services = self._resolve_region_services(name, mooncake) 

2311 if services.render_skipped: 

2312 self.store.update_region_status( 

2313 name, 

2314 self.region, 

2315 "creating", 

2316 error=services.error, 

2317 **status_conditions, 

2318 ) 

2319 return { 

2320 "action": "reconcile_mooncake", 

2321 "endpoint": name, 

2322 "deferred": "store_master_unresolved", 

2323 } 

2324 

2325 region_services = services.region_services or {} 

2326 

2327 # Step 2: keep the topology inside its own region. 

2328 scope = self._resolve_regional_scope(name, ns, spec, region_services) 

2329 if not scope.in_region: 

2330 self.store.update_region_status( 

2331 name, 

2332 self.region, 

2333 scope.state or "failed", 

2334 error=scope.error, 

2335 **status_conditions, 

2336 ) 

2337 return { 

2338 "action": "reconcile_mooncake", 

2339 "endpoint": name, 

2340 "failed": "cross_region_boundary", 

2341 } 

2342 

2343 # Step 3: gate dependent pods on the shared master (and its allow 

2344 # rules). This is also where the master itself is created if absent. 

2345 if self._needs_shared_master(mooncake): 

2346 gate = self._gate_on_mooncake_master(name, ns, spec) 

2347 if not gate.proceed: 

2348 self.store.update_region_status( 

2349 name, 

2350 self.region, 

2351 gate.state or "creating", 

2352 error=gate.error, 

2353 **status_conditions, 

2354 ) 

2355 return { 

2356 "action": "reconcile_mooncake", 

2357 "endpoint": name, 

2358 "deferred": "master_not_ready", 

2359 } 

2360 

2361 # Step 4: shared transport ConfigMap, applied once before role pods. 

2362 cfg = render_mooncake_config(mooncake, region_services) 

2363 self._ensure_mooncake_configmap(name, ns, cfg) 

2364 

2365 # Steps 5-6: converge ownership before creating/scaling any role 

2366 # Deployment. This is the same recreate/handoff barrier used by classic 

2367 # endpoints, extended to prefill/decode and stale topology roles. 

2368 desired_roles = self._desired_roles(mode) 

2369 ownership_results = [ 

2370 self._delete_autoscalers( 

2371 (name,), 

2372 (name, f"keda-hpa-{name}"), 

2373 ns, 

2374 ) 

2375 ] 

2376 for role in ("prefill", "decode"): 

2377 role_name = f"{name}-{role}" 

2378 if role in desired_roles: 

2379 ownership_results.append( 

2380 self._reconcile_role_autoscaler( 

2381 name, 

2382 ns, 

2383 spec, 

2384 role, 

2385 apply_desired=False, 

2386 ) 

2387 ) 

2388 else: 

2389 ownership_results.append( 

2390 self._delete_autoscalers( 

2391 (role_name,), 

2392 (role_name, f"keda-hpa-{role_name}"), 

2393 ns, 

2394 ) 

2395 ) 

2396 ownership = self._merge_cleanup_results(*ownership_results) 

2397 if not ownership.complete: 

2398 ready_total = 0 

2399 desired_total = 0 

2400 for role in desired_roles: 

2401 deploy_name = name if role == "single" else f"{name}-{role}" 

2402 deployment = self._get_deployment(deploy_name, ns) 

2403 deployment_status = getattr(deployment, "status", None) 

2404 deployment_spec = getattr(deployment, "spec", None) 

2405 ready_total += int(getattr(deployment_status, "ready_replicas", 0) or 0) 

2406 desired_total += int(getattr(deployment_spec, "replicas", 0) or 0) 

2407 status_kwargs: dict[str, Any] = {} 

2408 if ownership.error_message: 

2409 status_kwargs["error"] = ownership.error_message 

2410 self.store.update_region_status( 

2411 name, 

2412 self.region, 

2413 "updating", 

2414 replicas_ready=ready_total, 

2415 replicas_desired=desired_total, 

2416 **status_conditions, 

2417 **status_kwargs, 

2418 ) 

2419 return { 

2420 "action": "reconcile_mooncake_autoscaler", 

2421 "endpoint": name, 

2422 "cleanup_complete": False, 

2423 } 

2424 

2425 role_ready_total = 0 

2426 role_desired_total = 0 

2427 for role in desired_roles: 

2428 ready, desired, _restarted = self._ensure_role_deployment(name, ns, spec, role) 

2429 role_ready_total += ready 

2430 role_desired_total += desired 

2431 

2432 owner_verifications: list[ResourceCleanupResult] = [] 

2433 for role in ("prefill", "decode"): 

2434 role_config = self._role_autoscaling_config(spec, role) 

2435 if role not in desired_roles or role_config is None: 

2436 continue 

2437 self._create_role_hpa(name, ns, spec, role) 

2438 target_name = f"{name}-{role}" 

2439 metrics_config = role_config.get("metrics", [{"type": "cpu", "target": 70}]) 

2440 hpa_name = ( 

2441 f"keda-hpa-{target_name}" 

2442 if self._metrics_require_keda(metrics_config) 

2443 else target_name 

2444 ) 

2445 owner_verifications.append(self._verify_hpa_owner(hpa_name, ns, target_name)) 

2446 verified_owners = self._merge_cleanup_results(*owner_verifications) 

2447 if not verified_owners.complete: 

2448 verification_status_kwargs: dict[str, Any] = {} 

2449 if verified_owners.error_message: 

2450 verification_status_kwargs["error"] = verified_owners.error_message 

2451 self.store.update_region_status( 

2452 name, 

2453 self.region, 

2454 "updating", 

2455 replicas_ready=role_ready_total, 

2456 replicas_desired=role_desired_total, 

2457 **status_conditions, 

2458 **verification_status_kwargs, 

2459 ) 

2460 return { 

2461 "action": "reconcile_mooncake_autoscaler", 

2462 "endpoint": name, 

2463 "cleanup_complete": False, 

2464 } 

2465 

2466 # Step 7: front-end. Disaggregated and both run behind the proxy; store 

2467 # exposes its single Deployment directly. 

2468 if mode in ("disaggregated", "both"): 

2469 # Per-role Services so the proxy can address prefill and decode by 

2470 # stable in-cluster DNS. Routing through a Service means kube-proxy 

2471 # load-balances across only the Ready pods of each role, which is 

2472 # what gives the proxy ready-only decode routing for free. 

2473 role_port = spec.get("port", 8000) 

2474 for role in desired_roles: 

2475 self._create_role_service(name, ns, role, role_port) 

2476 try: 

2477 self._create_pd_proxy(name, ns, spec, endpoint) 

2478 except AdminApiKeySecretError as e: 

2479 logger.error("Proxy for endpoint %s in %s not started: %s", name, ns, e) 

2480 self.store.update_region_status( 

2481 name, 

2482 self.region, 

2483 "failed", 

2484 error=str(e), 

2485 **status_conditions, 

2486 ) 

2487 return { 

2488 "action": "reconcile_mooncake", 

2489 "endpoint": name, 

2490 "failed": "admin_api_key", 

2491 } 

2492 else: 

2493 self._create_service(name, ns, spec) 

2494 

2495 # Step 8: role-keyed status. 

2496 state = self._report_role_status( 

2497 name, 

2498 ns, 

2499 mooncake, 

2500 region_services, 

2501 endpoint, 

2502 ) 

2503 return {"action": "reconcile_mooncake", "endpoint": name, "state": state} 

2504 

2505 def _reconcile_stopped( 

2506 self, 

2507 name: str, 

2508 namespace: str, 

2509 spec: dict[str, Any] | None = None, 

2510 endpoint: dict[str, Any] | None = None, 

2511 ) -> dict[str, Any] | None: 

2512 """Remove every possible autoscaler owner before scaling all roles to zero.""" 

2513 mooncake_endpoint = isinstance(spec, dict) and isinstance(spec.get("mooncake"), dict) 

2514 self._unready_since.pop(name, None) 

2515 self._master_deferral_since.pop(name, None) 

2516 status_conditions = self._status_write_conditions(endpoint or {}) 

2517 role_names = (name, f"{name}-prefill", f"{name}-decode") 

2518 cleanup = self._delete_autoscalers( 

2519 role_names, 

2520 ( 

2521 name, 

2522 f"{name}-prefill", 

2523 f"{name}-decode", 

2524 f"keda-hpa-{name}", 

2525 f"keda-hpa-{name}-prefill", 

2526 f"keda-hpa-{name}-decode", 

2527 ), 

2528 namespace, 

2529 ) 

2530 deployment_names = (*role_names, f"{name}-proxy") if mooncake_endpoint else (name,) 

2531 deployments = { 

2532 deployment_name: self._get_deployment(deployment_name, namespace) 

2533 for deployment_name in deployment_names 

2534 } 

2535 ready_replicas = sum( 

2536 int(getattr(getattr(deployment, "status", None), "ready_replicas", 0) or 0) 

2537 for deployment in deployments.values() 

2538 if deployment is not None 

2539 ) 

2540 desired_replicas = sum( 

2541 int(getattr(getattr(deployment, "spec", None), "replicas", 0) or 0) 

2542 for deployment in deployments.values() 

2543 if deployment is not None 

2544 ) 

2545 if not cleanup.complete: 

2546 status_kwargs: dict[str, Any] = {} 

2547 if cleanup.error_message: 

2548 status_kwargs["error"] = cleanup.error_message 

2549 self.store.update_region_status( 

2550 name, 

2551 self.region, 

2552 "stopping", 

2553 replicas_ready=ready_replicas, 

2554 replicas_desired=desired_replicas, 

2555 **status_conditions, 

2556 **status_kwargs, 

2557 ) 

2558 return {"action": "stop", "endpoint": name, "cleanup_complete": False} 

2559 

2560 scaled = False 

2561 for deployment_name, deployment in deployments.items(): 

2562 if deployment is None: 

2563 continue 

2564 current_replicas = int(getattr(getattr(deployment, "spec", None), "replicas", 0) or 0) 

2565 if current_replicas <= 0: 

2566 continue 

2567 logger.info("Stopping endpoint role %s (scaling to 0)", deployment_name) 

2568 self._scale_deployment(deployment_name, namespace, 0) 

2569 scaled = True 

2570 

2571 self.store.update_region_status( 

2572 name, 

2573 self.region, 

2574 "stopped", 

2575 replicas_ready=0, 

2576 replicas_desired=0, 

2577 **status_conditions, 

2578 ) 

2579 if scaled: 

2580 return {"action": "stop", "endpoint": name, "cleanup_complete": True} 

2581 return None 

2582 

2583 def _reconcile_deleted( 

2584 self, 

2585 endpoint: dict[str, Any], 

2586 namespace: str, 

2587 spec: dict[str, Any] | None = None, 

2588 ) -> dict[str, Any]: 

2589 """Converge all parents and generated children to stable absence.""" 

2590 name = endpoint["endpoint_name"] 

2591 lifecycle_id = endpoint.get("lifecycle_id") 

2592 if not isinstance(lifecycle_id, str) or not lifecycle_id: 

2593 raise RuntimeError("Endpoint deletion requires an immutable lifecycle id") 

2594 self._unready_since.pop(name, None) 

2595 self._master_deferral_since.pop(name, None) 

2596 logger.info("Reconciling deletion of endpoint %s from %s", name, self.region) 

2597 cleanup = self._delete_resources( 

2598 name, 

2599 namespace, 

2600 spec, 

2601 expected_lifecycle_id=lifecycle_id, 

2602 ) 

2603 state, written = self._record_cleanup_observation(endpoint, cleanup) 

2604 return { 

2605 "action": "delete", 

2606 "endpoint": name, 

2607 "cleanup_complete": state == "deleted" and written, 

2608 } 

2609 

2610 # ------------------------------------------------------------------ 

2611 # Kubernetes resource management 

2612 # ------------------------------------------------------------------ 

2613 

2614 def _deployment_exists(self, name: str, namespace: str) -> bool: 

2615 return self._get_deployment(name, namespace) is not None 

2616 

2617 def _get_deployment(self, name: str, namespace: str) -> V1Deployment | None: 

2618 try: 

2619 deployment = self.apps_v1.read_namespaced_deployment( 

2620 name, namespace, _request_timeout=self._k8s_timeout 

2621 ) 

2622 except ApiException as error: 

2623 if error.status == 404: 

2624 return None 

2625 raise 

2626 return self._authorize_resource( 

2627 deployment, 

2628 kind="deployment", 

2629 resource_name=name, 

2630 patch_metadata=partial( 

2631 self.apps_v1.patch_namespaced_deployment, 

2632 name, 

2633 namespace, 

2634 _request_timeout=self._k8s_timeout, 

2635 ), 

2636 read_resource=lambda: self.apps_v1.read_namespaced_deployment( 

2637 name, namespace, _request_timeout=self._k8s_timeout 

2638 ), 

2639 delete_resource=partial( 

2640 self.apps_v1.delete_namespaced_deployment, 

2641 name, 

2642 namespace, 

2643 _request_timeout=self._k8s_timeout, 

2644 ), 

2645 ) 

2646 

2647 def _get_deployment_image(self, deployment: V1Deployment) -> str | None: 

2648 """Get the image of the first container in a deployment.""" 

2649 containers = deployment.spec.template.spec.containers 

2650 if containers: 

2651 image: str = containers[0].image 

2652 return image 

2653 return None 

2654 

2655 def _resolve_image_for_region(self, spec: dict[str, Any]) -> str: 

2656 """Pick the image URI this region should pull from. 

2657 

2658 ``cli.inference.InferenceManager.deploy`` populates 

2659 ``spec["region_image_uris"]`` with a per-region map when the 

2660 primary image is an ECR URI, so each cluster can pull from its 

2661 local replica instead of crossing the WAN. The map is omitted 

2662 for non-ECR refs and for deploys with ``rewrite_image=False``, 

2663 in which case we fall back to the flat ``spec["image"]`` URI. 

2664 

2665 When the map is present but lacks an entry for ``self.region`` 

2666 (a target region was added after the spec was last written), 

2667 the flat URI is also used so the deployment doesn't break — the 

2668 next reconcile after a fresh deploy picks up the right URI. 

2669 """ 

2670 region_map = spec.get("region_image_uris") 

2671 if isinstance(region_map, dict): 

2672 uri = region_map.get(self.region) 

2673 if isinstance(uri, str) and uri: 

2674 return uri 

2675 return str(spec["image"]) 

2676 

2677 # ------------------------------------------------------------------ 

2678 # In-region service resolution (master address + cold-tier bucket) 

2679 # ------------------------------------------------------------------ 

2680 

2681 def _resolve_region_services( 

2682 self, name: str, mooncake: dict[str, Any] 

2683 ) -> RegionServicesResolution: 

2684 """Resolve the in-region addresses an endpoint's ``mooncake.json`` needs. 

2685 

2686 Everything an endpoint wires to is resolved for the monitor's own 

2687 region from regional configuration — never from values typed into the 

2688 endpoint spec: 

2689 

2690 - The shared master's RPC address comes from regional configuration. It 

2691 is required whenever the store is enabled. When the store is enabled 

2692 and no own-region master address is configured, rendering is skipped 

2693 and the existing endpoint configuration is left unchanged; the result 

2694 records the unresolved-master condition so the caller can report it. 

2695 - The metadata server defaults to the master host on the metadata port 

2696 unless regional configuration supplies one explicitly. 

2697 - When the cold tier is opted in (``store.cold_tier_enabled`` is the 

2698 boolean ``True``), the cold-tier object-store URI is resolved to the 

2699 own-region general-purpose regional bucket from that region's 

2700 ``/name`` discovery value. Any cold-tier bucket URI in the spec is 

2701 ignored. Whether the endpoint writes to the cold tier is governed 

2702 solely by the per-endpoint flag, independent of the always-on bucket. 

2703 When the bucket cannot be resolved (the region's stack is not yet 

2704 deployed), the cold tier is dropped and the condition is recorded, 

2705 while the hot-path store stays configured. 

2706 

2707 Args: 

2708 name: The endpoint name, used to scope the cold-tier object key. 

2709 mooncake: The ``spec["mooncake"]`` block. 

2710 

2711 Returns: 

2712 A :class:`RegionServicesResolution` carrying the resolved 

2713 ``region_services`` dict and any skip/unresolved signals. 

2714 """ 

2715 store = mooncake.get("store", {}) 

2716 store_enabled = bool(store.get("enabled")) 

2717 

2718 master_address = os.environ.get(MOONCAKE_MASTER_ADDRESS_ENV, "").strip() 

2719 

2720 # The store needs an own-region master. It is a fixed in-cluster Service 

2721 # the monitor itself provisions per region (mooncake-master:50051), so 

2722 # when no override is set in the environment, default to that Service 

2723 # rather than deferring — the address is known by construction. An 

2724 # operator may still override it via MOONCAKE_MASTER_ADDRESS. 

2725 if store_enabled and not master_address: 

2726 master_address = f"{MOONCAKE_MASTER_SERVICE}:{MOONCAKE_MASTER_RPC_PORT}" 

2727 

2728 region_services: dict[str, Any] = { 

2729 "metadata_server": self._metadata_server_url(master_address), 

2730 } 

2731 if store_enabled: 

2732 region_services["master_server_address"] = master_address 

2733 

2734 result = RegionServicesResolution(region_services=region_services) 

2735 

2736 # Cold tier is opt-in per endpoint. The bucket is always resolved for 

2737 # the monitor's own region; any URI supplied in the spec is ignored. 

2738 if store_enabled and store.get("cold_tier_enabled") is True: 

2739 bucket = self._resolve_regional_shared_bucket() 

2740 if bucket: 

2741 region_services["cold_tier_s3_uri"] = ( 

2742 f"s3://{bucket}/{MOONCAKE_COLD_TIER_KEY_PREFIX}/{name}/" 

2743 ) 

2744 else: 

2745 # The own-region general-purpose bucket is not resolvable yet. 

2746 # Drop the cold tier but keep the hot-path store operating. 

2747 result.cold_tier_unresolved = True 

2748 result.error = ( 

2749 "general-purpose regional bucket for region " 

2750 f"{self.region} could not be resolved; cold tier disabled, " 

2751 "hot-path store still active" 

2752 ) 

2753 

2754 return result 

2755 

2756 def _metadata_server_url(self, master_address: str) -> str: 

2757 """Return the metadata server URL, deriving it from the master host. 

2758 

2759 Regional configuration may supply the metadata server URL directly. 

2760 When it does not, the URL defaults to the master host on the metadata 

2761 port; if no master host is known the conventional in-cluster service 

2762 name is used. 

2763 """ 

2764 configured = os.environ.get(MOONCAKE_METADATA_SERVER_ENV, "").strip() 

2765 if configured: 

2766 return configured 

2767 host = master_address.rsplit(":", 1)[0] if master_address else MOONCAKE_MASTER_SERVICE 

2768 return f"http://{host}:{MOONCAKE_METADATA_PORT}/metadata" 

2769 

2770 def _resolve_regional_shared_bucket(self) -> str | None: 

2771 """Resolve the own-region general-purpose bucket name, or ``None``. 

2772 

2773 Reads the monitor's own region's ``/name`` discovery value for the 

2774 always-on general-purpose regional bucket. Returns ``None`` when the 

2775 value is absent (the region's stack is not yet deployed) or cannot be 

2776 read, so the caller can drop the cold tier without disturbing the 

2777 hot-path store. 

2778 """ 

2779 from gco.services.aws_ssm import get_ssm_parameter_optional 

2780 

2781 param_name = f"{_regional_shared_ssm_parameter_prefix()}/name" 

2782 try: 

2783 bucket = get_ssm_parameter_optional(param_name, region=self.region) 

2784 return bucket if isinstance(bucket, str) and bucket else None 

2785 except Exception as e: # noqa: BLE001 - any read failure means "unresolved" 

2786 logger.warning( 

2787 "Failed to resolve general-purpose regional bucket for %s: %s", 

2788 self.region, 

2789 e, 

2790 ) 

2791 return None 

2792 

2793 # ------------------------------------------------------------------ 

2794 # Regional scope boundary (intra-region RDMA enforcement) 

2795 # ------------------------------------------------------------------ 

2796 

2797 def _region_of_address(self, address: str) -> str: 

2798 """Return the address's explicit AWS region or safe local classification. 

2799 

2800 AWS region tokens embedded in the host are authoritative. Bare Service 

2801 names and Kubernetes ``.svc`` names are local by construction. Any 

2802 other host without a region token is external and ambiguous, so it is 

2803 classified as ``"unknown"`` and rejected by regional-scope checks. 

2804 """ 

2805 candidate = (address or "").strip() 

2806 if not candidate: 

2807 return "unknown" 

2808 

2809 try: 

2810 parsed = urlsplit(candidate if "://" in candidate else f"//{candidate}") 

2811 host = (parsed.hostname or "").rstrip(".").lower() 

2812 except ValueError: 

2813 return "unknown" 

2814 if not host: 

2815 return "unknown" 

2816 

2817 match = _REGION_TOKEN_PATTERN.search(host) 

2818 if match: 

2819 return match.group(0) 

2820 if "." not in host or host.endswith((".svc", ".svc.cluster.local")): 

2821 return self.region 

2822 return "unknown" 

2823 

2824 def _resolve_regional_scope( 

2825 self, 

2826 name: str, 

2827 ns: str, 

2828 spec: dict[str, Any], 

2829 region_services: dict[str, Any] | None, 

2830 ) -> RegionalScopeResolution: 

2831 """Confirm a disaggregated topology wires only to its own region. 

2832 

2833 Gathers every address the own-region topology connects to — the 

2834 ``MooncakeConnector`` peers (the sibling role Services for prefill and 

2835 decode), the shared master's RPC address, and the metadata server — and 

2836 confirms each resolves to the monitor's own region. Any explicitly 

2837 supplied peer addresses in the spec are checked too, so a misconfigured 

2838 endpoint that points a peer or master at another region is caught 

2839 before any role pod is materialized. 

2840 

2841 An endpoint that enumerates two or more target regions runs one 

2842 independent topology per region: each region's monitor reconciles only 

2843 its own topology (it reconciles only while ``self.region`` is one of the 

2844 target regions), and this resolution confirms that topology's addresses 

2845 never cross into another region. 

2846 

2847 Args: 

2848 name: The endpoint name; used to derive the in-cluster peer Service 

2849 names for the disaggregated roles. 

2850 ns: The namespace the topology materializes into. 

2851 spec: The endpoint spec being reconciled. 

2852 region_services: The resolved in-region addresses (from 

2853 :meth:`_resolve_region_services`), or ``None`` when none were 

2854 resolved. Supplies the master and metadata addresses to check. 

2855 

2856 Returns: 

2857 A :class:`RegionalScopeResolution`. When every resolved address is 

2858 own-region, ``in_region`` is ``True`` and the caller may materialize 

2859 the role Deployments. When any address resolves to another region, 

2860 ``in_region`` is ``False``, ``state`` is ``"failed"``, and ``error`` 

2861 names the offending addresses; the caller then materializes no role 

2862 Deployments and leaves any prior resources unchanged. 

2863 """ 

2864 mooncake = spec.get("mooncake") or {} 

2865 mode = mooncake.get("mode") 

2866 roles = _WORKER_ROLES_BY_MODE.get(mode, set()) if isinstance(mode, str) else set() 

2867 

2868 # MooncakeConnector peers are the sibling role Services within this 

2869 # namespace; collect them in a stable order for deterministic reporting. 

2870 addresses: list[str] = [] 

2871 if "prefill" in roles or "decode" in roles: 

2872 for role in ("prefill", "decode"): 

2873 addresses.append(f"{name}-{role}.{ns}.svc.cluster.local") 

2874 

2875 # The shared master and metadata server the pods reach. These come from 

2876 # the own-region resolution, but are checked here so a foreign address 

2877 # supplied through regional configuration is still caught. 

2878 if region_services: 

2879 master = region_services.get("master_server_address") 

2880 metadata = region_services.get("metadata_server") 

2881 if master: 

2882 addresses.append(str(master)) 

2883 if metadata: 

2884 addresses.append(str(metadata)) 

2885 

2886 # Defensive: honor any explicit peer/master addresses authored on the 

2887 # spec so a hand-edited endpoint cannot smuggle in an out-of-region peer. 

2888 store = mooncake.get("store") or {} 

2889 transfer = mooncake.get("transfer") or {} 

2890 for candidate in ( 

2891 store.get("master_server_address"), 

2892 store.get("metadata_server"), 

2893 ): 

2894 if candidate: 

2895 addresses.append(str(candidate)) 

2896 explicit_peers = transfer.get("peer_addresses") 

2897 if isinstance(explicit_peers, list): 

2898 addresses.extend(str(peer) for peer in explicit_peers if peer) 

2899 

2900 # De-duplicate while preserving first-seen order. 

2901 seen: set[str] = set() 

2902 ordered: list[str] = [] 

2903 for address in addresses: 

2904 if address not in seen: 

2905 seen.add(address) 

2906 ordered.append(address) 

2907 

2908 resolved_regions = [(address, self._region_of_address(address)) for address in ordered] 

2909 out_of_region = [ 

2910 (address, region) for address, region in resolved_regions if region != self.region 

2911 ] 

2912 

2913 if out_of_region: 

2914 detail = ", ".join( 

2915 f"{address!r} resolves to region {region}" for address, region in out_of_region 

2916 ) 

2917 logger.error( 

2918 "Cross-region boundary violation for endpoint %s in %s: %s", 

2919 name, 

2920 self.region, 

2921 detail, 

2922 ) 

2923 return RegionalScopeResolution( 

2924 in_region=False, 

2925 peer_addresses=ordered, 

2926 state="failed", 

2927 error=(f"cross-region boundary violation: {detail}; expected region {self.region}"), 

2928 ) 

2929 

2930 return RegionalScopeResolution(in_region=True, peer_addresses=ordered) 

2931 

2932 def _ensure_mooncake_store(self, ns: str, spec: dict[str, Any]) -> None: 

2933 """Maintain the single shared per-region Mooncake master, idempotently. 

2934 

2935 The master is region-shared, not per-endpoint: every endpoint that 

2936 needs the store reaches the same ``mooncake-master`` StatefulSet and the 

2937 headless Service that fronts its RPC and metadata ports. This method 

2938 uses create-if-absent semantics, so any number of calls within a region 

2939 converge on exactly one StatefulSet with a single replica and one 

2940 Service. An already-existing master is left untouched — a conflicting 

2941 create is treated as success and never overwrites the running master. 

2942 

2943 The StatefulSet runs the master daemon with its built-in HTTP metadata 

2944 server, exposing RPC on :data:`MOONCAKE_MASTER_RPC_PORT` and the 

2945 metadata endpoint on :data:`MOONCAKE_METADATA_PORT`. Both ports are 

2946 published on the headless Service so in-namespace pods can resolve them. 

2947 

2948 Args: 

2949 ns: The namespace the master shares with the inference workloads. 

2950 spec: The endpoint spec being reconciled. Its ``mooncake`` block may 

2951 carry a master image override; otherwise the image is taken from 

2952 the in-region deployment's environment. 

2953 """ 

2954 mooncake = spec.get("mooncake", {}) or {} 

2955 store = mooncake.get("store", {}) or {} 

2956 image = store.get("master_image") or os.environ.get(MOONCAKE_MASTER_IMAGE_ENV, "").strip() 

2957 

2958 labels = {"app": MOONCAKE_MASTER_SERVICE, "project": "gco"} 

2959 

2960 # Headless Service exposing both the RPC and metadata ports. A None 

2961 # cluster IP keeps it headless so the StatefulSet's stable network 

2962 # identity resolves directly. 

2963 service = client.V1Service( 

2964 metadata=client.V1ObjectMeta( 

2965 name=MOONCAKE_MASTER_SERVICE, 

2966 namespace=ns, 

2967 labels=labels, 

2968 ), 

2969 spec=client.V1ServiceSpec( 

2970 cluster_ip="None", 

2971 selector={"app": MOONCAKE_MASTER_SERVICE}, 

2972 ports=[ 

2973 client.V1ServicePort( 

2974 name="rpc", 

2975 port=MOONCAKE_MASTER_RPC_PORT, 

2976 target_port="rpc", 

2977 protocol="TCP", 

2978 ), 

2979 client.V1ServicePort( 

2980 name="metadata", 

2981 port=MOONCAKE_METADATA_PORT, 

2982 target_port="metadata", 

2983 protocol="TCP", 

2984 ), 

2985 ], 

2986 ), 

2987 ) 

2988 

2989 container = client.V1Container( 

2990 name=MOONCAKE_MASTER_SERVICE, 

2991 image=image, 

2992 command=["mooncake_master"], 

2993 args=[ 

2994 f"--port={MOONCAKE_MASTER_RPC_PORT}", 

2995 "--enable_http_metadata_server=true", 

2996 f"--http_metadata_server_port={MOONCAKE_METADATA_PORT}", 

2997 ], 

2998 ports=[ 

2999 client.V1ContainerPort( 

3000 name="rpc", 

3001 container_port=MOONCAKE_MASTER_RPC_PORT, 

3002 protocol="TCP", 

3003 ), 

3004 client.V1ContainerPort( 

3005 name="metadata", 

3006 container_port=MOONCAKE_METADATA_PORT, 

3007 protocol="TCP", 

3008 ), 

3009 ], 

3010 security_context=client.V1SecurityContext( 

3011 allow_privilege_escalation=False, 

3012 # The upstream mooncake_master launcher chmods its bundled 

3013 # binary on startup, which needs a writable root filesystem (a 

3014 # read-only root raised OSError: Read-only file system). The pod 

3015 # also runs as root so the chmod of the root-owned binary is 

3016 # permitted. Privilege escalation stays disabled and all 

3017 # capabilities are dropped, so this is constrained root. 

3018 read_only_root_filesystem=False, 

3019 capabilities=client.V1Capabilities(drop=["ALL"]), 

3020 ), 

3021 resources=client.V1ResourceRequirements( 

3022 requests={"cpu": "250m", "memory": "512Mi"}, 

3023 limits={"cpu": "1", "memory": "2Gi"}, 

3024 ), 

3025 startup_probe=client.V1Probe( 

3026 tcp_socket=client.V1TCPSocketAction(port="rpc"), 

3027 initial_delay_seconds=5, 

3028 period_seconds=5, 

3029 failure_threshold=30, 

3030 ), 

3031 liveness_probe=client.V1Probe( 

3032 tcp_socket=client.V1TCPSocketAction(port="rpc"), 

3033 initial_delay_seconds=15, 

3034 period_seconds=30, 

3035 ), 

3036 readiness_probe=client.V1Probe( 

3037 # The HTTP metadata server returns 400 for a bare GET /metadata 

3038 # (it expects a ?key=), so an HTTP GET readiness probe never 

3039 # passes. Confirm readiness by checking the metadata port is 

3040 # accepting connections instead. 

3041 tcp_socket=client.V1TCPSocketAction(port="metadata"), 

3042 initial_delay_seconds=10, 

3043 period_seconds=15, 

3044 ), 

3045 ) 

3046 

3047 stateful_set = client.V1StatefulSet( 

3048 metadata=client.V1ObjectMeta( 

3049 name=MOONCAKE_MASTER_SERVICE, 

3050 namespace=ns, 

3051 labels=labels, 

3052 ), 

3053 spec=client.V1StatefulSetSpec( 

3054 service_name=MOONCAKE_MASTER_SERVICE, 

3055 replicas=1, 

3056 selector=client.V1LabelSelector(match_labels={"app": MOONCAKE_MASTER_SERVICE}), 

3057 template=client.V1PodTemplateSpec( 

3058 metadata=client.V1ObjectMeta( 

3059 labels=labels, 

3060 # Keep Karpenter from consolidating the node out from 

3061 # under the master: it is a single-replica, stateful 

3062 # control-plane daemon holding KV metadata for every 

3063 # in-region endpoint, so an eviction drops that state and 

3064 # disrupts inference. On lightly-loaded clusters 

3065 # consolidation otherwise evicts it mid-image-pull before 

3066 # it can even start. 

3067 annotations={"karpenter.sh/do-not-disrupt": "true"}, 

3068 ), 

3069 spec=client.V1PodSpec( 

3070 service_account_name="gco-service-account", 

3071 automount_service_account_token=False, 

3072 # The upstream mooncake_master launcher chmods its 

3073 # bundled binary (root-owned in the image) on startup, so 

3074 # the master must run as root: a non-root uid cannot 

3075 # chmod a root-owned file ("Operation not permitted"). 

3076 # gco-inference does not enforce restricted Pod Security 

3077 # and the no-root rule applies only to user-submitted 

3078 # jobs, so a root platform daemon is consistent here. The 

3079 # container still drops all Linux capabilities and 

3080 # disallows privilege escalation (see its securityContext). 

3081 security_context=client.V1PodSecurityContext( 

3082 run_as_user=0, 

3083 run_as_group=0, 

3084 ), 

3085 containers=[container], 

3086 restart_policy="Always", 

3087 ), 

3088 ), 

3089 ), 

3090 ) 

3091 

3092 # Create-if-absent: a 409 means the shared master already exists, which 

3093 # is the steady state. Leave it untouched and treat it as success. 

3094 self._assert_mutation_authority() 

3095 try: 

3096 self.core_v1.create_namespaced_service(ns, service, _request_timeout=self._k8s_timeout) 

3097 logger.info("Created shared mooncake master service in %s", ns) 

3098 except ApiException as e: 

3099 if e.status == 409: 

3100 logger.info("Shared mooncake master service already exists in %s", ns) 

3101 else: 

3102 raise 

3103 

3104 self._assert_mutation_authority() 

3105 try: 

3106 self.apps_v1.create_namespaced_stateful_set( 

3107 ns, stateful_set, _request_timeout=self._k8s_timeout 

3108 ) 

3109 logger.info("Created shared mooncake master statefulset in %s", ns) 

3110 except ApiException as e: 

3111 if e.status == 409: 

3112 logger.info("Shared mooncake master statefulset already exists in %s", ns) 

3113 else: 

3114 raise 

3115 

3116 def _mooncake_master_ready_replicas(self, ns: str) -> int: 

3117 """Return the shared master's Ready replica count, 0 when absent. 

3118 

3119 Reads the ``mooncake-master`` StatefulSet status in ``ns``. A missing 

3120 StatefulSet (404) reports zero Ready replicas rather than raising, so a 

3121 caller gating on readiness simply keeps deferring until it appears. 

3122 

3123 Args: 

3124 ns: The namespace the shared master lives in. 

3125 

3126 Returns: 

3127 The number of Ready replicas the StatefulSet reports, or 0 when it 

3128 does not yet exist or reports no Ready replicas. 

3129 """ 

3130 try: 

3131 status = self.apps_v1.read_namespaced_stateful_set_status( 

3132 MOONCAKE_MASTER_SERVICE, ns, _request_timeout=self._k8s_timeout 

3133 ) 

3134 except ApiException as e: 

3135 if e.status == 404: 

3136 return 0 

3137 raise 

3138 

3139 ready = getattr(getattr(status, "status", None), "ready_replicas", 0) 

3140 return int(ready or 0) 

3141 

3142 def _gate_on_mooncake_master( 

3143 self, name: str, ns: str, spec: dict[str, Any] 

3144 ) -> MasterReadinessGate: 

3145 """Gate dependent role-pod creation on the shared master's readiness. 

3146 

3147 Maintains the single shared master (create-if-absent) and then decides 

3148 whether the endpoint's dependent role pods may be materialized: 

3149 

3150 - If maintaining the master fails, no dependent pods are materialized, 

3151 any existing master is left unmodified, and the endpoint stays in 

3152 ``creating`` carrying a create-failure error. 

3153 - While the master reports fewer than 1 Ready replica, creation is 

3154 deferred and the endpoint stays in ``creating``. The first deferral 

3155 starts a clock; once it exceeds the wait window the endpoint keeps 

3156 deferring and stays in ``creating`` but also surfaces a not-ready 

3157 error. The master is never deleted or modified on account of the 

3158 timeout. 

3159 - Once the master reports at least 1 Ready replica, the gate opens: the 

3160 clock is cleared and the caller may create the role pods and advance 

3161 out of ``creating``. 

3162 

3163 Args: 

3164 name: The endpoint name, used to track its first deferral. 

3165 ns: The namespace the master shares with the workloads. 

3166 spec: The endpoint spec being reconciled. 

3167 

3168 Returns: 

3169 A :class:`MasterReadinessGate` describing whether to proceed, the 

3170 endpoint state to report, and any error to surface. 

3171 """ 

3172 # Maintain the shared master first. A create failure must not produce 

3173 # any dependent pods and must leave an existing master untouched. 

3174 try: 

3175 self._ensure_mooncake_store(ns, spec) 

3176 except ApiException as e: 

3177 logger.error( 

3178 "Could not create shared mooncake master in %s for endpoint %s: %s", 

3179 ns, 

3180 name, 

3181 e, 

3182 ) 

3183 return MasterReadinessGate( 

3184 proceed=False, 

3185 state="creating", 

3186 error="shared master could not be created", 

3187 ) 

3188 

3189 # Apply the intra-namespace allow rules before any role pod is created. 

3190 # A failure here must fail pod materialization while leaving the 

3191 # default-deny posture intact; surface which rule could not be applied. 

3192 try: 

3193 self._ensure_intra_namespace_network_policies(ns, spec) 

3194 except NetworkPolicyApplyError as e: 

3195 logger.error( 

3196 "Could not apply network policy %s in %s for endpoint %s: %s", 

3197 e.rule, 

3198 ns, 

3199 name, 

3200 e.reason, 

3201 ) 

3202 return MasterReadinessGate( 

3203 proceed=False, 

3204 state="creating", 

3205 error=f"network policy {e.rule} could not be applied", 

3206 ) 

3207 

3208 ready_replicas = self._mooncake_master_ready_replicas(ns) 

3209 if ready_replicas >= 1: 

3210 # Master is Ready: open the gate and reset the deferral clock so a 

3211 # later master restart restarts the wait window cleanly. 

3212 if name in self._master_deferral_since: 

3213 logger.info( 

3214 "Shared master Ready in %s, resuming creation for endpoint %s", 

3215 ns, 

3216 name, 

3217 ) 

3218 del self._master_deferral_since[name] 

3219 return MasterReadinessGate(proceed=True, state=None, error=None) 

3220 

3221 # Master not Ready: defer creation and report creating. Start the clock 

3222 # on the first deferral. 

3223 now = datetime.now(UTC) 

3224 first_deferral = self._master_deferral_since.setdefault(name, now) 

3225 deferred_for = (now - first_deferral).total_seconds() 

3226 

3227 if deferred_for >= MOONCAKE_MASTER_READY_TIMEOUT_SECONDS: 

3228 logger.error( 

3229 "Shared master not Ready in %s after %.0fs, still deferring endpoint %s", 

3230 ns, 

3231 deferred_for, 

3232 name, 

3233 ) 

3234 return MasterReadinessGate( 

3235 proceed=False, 

3236 state="creating", 

3237 error="shared master did not become Ready", 

3238 ) 

3239 

3240 logger.info( 

3241 "Deferring creation for endpoint %s in %s until shared master is Ready", 

3242 name, 

3243 ns, 

3244 ) 

3245 return MasterReadinessGate(proceed=False, state="creating", error=None) 

3246 

3247 def _ensure_intra_namespace_network_policies(self, ns: str, spec: dict[str, Any]) -> None: 

3248 """Apply the intra-namespace allow rules disaggregated inference needs. 

3249 

3250 Alongside the default-deny posture in ``gco-inference`` (defined in 

3251 ``03-network-policies.yaml`` and never touched here), this maintains 

3252 four widening allow rules with create-if-absent semantics: 

3253 

3254 - ``allow-inference-internal`` — managed inference pods exchange TCP 

3255 traffic and may reach the shared master's two fixed ports. This 

3256 permits proxy-to-role serving and bootstrap traffic while excluding 

3257 unselected sources such as the ALB. 

3258 - ``allow-pod-to-master`` — inference pods reach the shared master RPC 

3259 port (:data:`MOONCAKE_MASTER_RPC_PORT`). 

3260 - ``allow-pod-to-metadata`` — inference pods reach the shared metadata 

3261 server (:data:`MOONCAKE_METADATA_PORT`). 

3262 - ``allow-rdma-bootstrap`` — inference pods reach each other on the 

3263 contiguous KV-transfer bootstrap port window starting at the spec's 

3264 ``mooncake.transfer.bootstrap_base_port`` (default 

3265 :data:`MOONCAKE_BOOTSTRAP_BASE_PORT`). 

3266 

3267 Each rule is created independently; an already-present rule (409) is the 

3268 steady state and counts as success. No deny rule is ever read, modified, 

3269 or deleted, so the default-deny policy is preserved regardless of 

3270 outcome. 

3271 

3272 Args: 

3273 ns: The inference namespace the rules apply to. 

3274 spec: The endpoint spec being reconciled; its 

3275 ``mooncake.transfer.bootstrap_base_port`` sizes the bootstrap 

3276 port window. 

3277 

3278 Raises: 

3279 NetworkPolicyApplyError: If any single rule cannot be created. The 

3280 error names the failing rule; rules created before the failure 

3281 remain in place and the default-deny policy is untouched. 

3282 """ 

3283 transfer = (spec.get("mooncake", {}) or {}).get("transfer", {}) or {} 

3284 base_port = transfer.get("bootstrap_base_port", MOONCAKE_BOOTSTRAP_BASE_PORT) 

3285 try: 

3286 base_port = int(base_port) 

3287 except TypeError, ValueError: 

3288 base_port = MOONCAKE_BOOTSTRAP_BASE_PORT 

3289 end_port = min(base_port + MOONCAKE_BOOTSTRAP_PORT_SPAN, MAX_BOOTSTRAP_PORT) 

3290 

3291 labels = {"project": "gco"} 

3292 master_selector = client.V1LabelSelector(match_labels={"app": MOONCAKE_MASTER_SERVICE}) 

3293 inference_selector = client.V1LabelSelector(match_labels=INFERENCE_POD_SELECTOR) 

3294 inference_peer = [client.V1NetworkPolicyPeer(pod_selector=inference_selector)] 

3295 master_peer = [client.V1NetworkPolicyPeer(pod_selector=master_selector)] 

3296 all_tcp = [client.V1NetworkPolicyPort(protocol="TCP")] 

3297 

3298 policies = [ 

3299 ( 

3300 NETWORK_POLICY_INFERENCE_INTERNAL, 

3301 client.V1NetworkPolicy( 

3302 metadata=client.V1ObjectMeta( 

3303 name=NETWORK_POLICY_INFERENCE_INTERNAL, namespace=ns, labels=labels 

3304 ), 

3305 spec=client.V1NetworkPolicySpec( 

3306 pod_selector=inference_selector, 

3307 policy_types=["Ingress", "Egress"], 

3308 ingress=[ 

3309 client.V1NetworkPolicyIngressRule( 

3310 _from=inference_peer, 

3311 ports=all_tcp, 

3312 ) 

3313 ], 

3314 egress=[ 

3315 client.V1NetworkPolicyEgressRule( 

3316 to=inference_peer, 

3317 ports=all_tcp, 

3318 ), 

3319 client.V1NetworkPolicyEgressRule( 

3320 to=master_peer, 

3321 ports=[ 

3322 client.V1NetworkPolicyPort( 

3323 protocol="TCP", port=MOONCAKE_MASTER_RPC_PORT 

3324 ), 

3325 client.V1NetworkPolicyPort( 

3326 protocol="TCP", port=MOONCAKE_METADATA_PORT 

3327 ), 

3328 ], 

3329 ), 

3330 ], 

3331 ), 

3332 ), 

3333 ), 

3334 ( 

3335 NETWORK_POLICY_POD_TO_MASTER, 

3336 client.V1NetworkPolicy( 

3337 metadata=client.V1ObjectMeta( 

3338 name=NETWORK_POLICY_POD_TO_MASTER, namespace=ns, labels=labels 

3339 ), 

3340 spec=client.V1NetworkPolicySpec( 

3341 pod_selector=master_selector, 

3342 policy_types=["Ingress"], 

3343 ingress=[ 

3344 client.V1NetworkPolicyIngressRule( 

3345 _from=inference_peer, 

3346 ports=[ 

3347 client.V1NetworkPolicyPort( 

3348 protocol="TCP", port=MOONCAKE_MASTER_RPC_PORT 

3349 ) 

3350 ], 

3351 ) 

3352 ], 

3353 ), 

3354 ), 

3355 ), 

3356 ( 

3357 NETWORK_POLICY_POD_TO_METADATA, 

3358 client.V1NetworkPolicy( 

3359 metadata=client.V1ObjectMeta( 

3360 name=NETWORK_POLICY_POD_TO_METADATA, namespace=ns, labels=labels 

3361 ), 

3362 spec=client.V1NetworkPolicySpec( 

3363 pod_selector=master_selector, 

3364 policy_types=["Ingress"], 

3365 ingress=[ 

3366 client.V1NetworkPolicyIngressRule( 

3367 _from=inference_peer, 

3368 ports=[ 

3369 client.V1NetworkPolicyPort( 

3370 protocol="TCP", port=MOONCAKE_METADATA_PORT 

3371 ) 

3372 ], 

3373 ) 

3374 ], 

3375 ), 

3376 ), 

3377 ), 

3378 ( 

3379 NETWORK_POLICY_RDMA_BOOTSTRAP, 

3380 client.V1NetworkPolicy( 

3381 metadata=client.V1ObjectMeta( 

3382 name=NETWORK_POLICY_RDMA_BOOTSTRAP, namespace=ns, labels=labels 

3383 ), 

3384 spec=client.V1NetworkPolicySpec( 

3385 pod_selector=inference_selector, 

3386 policy_types=["Ingress"], 

3387 ingress=[ 

3388 client.V1NetworkPolicyIngressRule( 

3389 _from=inference_peer, 

3390 ports=[ 

3391 client.V1NetworkPolicyPort( 

3392 protocol="TCP", 

3393 port=base_port, 

3394 end_port=end_port, 

3395 ) 

3396 ], 

3397 ) 

3398 ], 

3399 ), 

3400 ), 

3401 ), 

3402 ] 

3403 

3404 for rule_name, policy in policies: 

3405 self._assert_mutation_authority() 

3406 try: 

3407 self.networking_v1.create_namespaced_network_policy( 

3408 ns, policy, _request_timeout=self._k8s_timeout 

3409 ) 

3410 logger.info("Applied network policy %s in %s", rule_name, ns) 

3411 except ApiException as e: 

3412 if e.status == 409: 

3413 # Already present — the steady state. Leave it untouched. 

3414 logger.info("Network policy %s already present in %s", rule_name, ns) 

3415 continue 

3416 logger.error( 

3417 "Could not apply network policy %s in %s: %s", 

3418 rule_name, 

3419 ns, 

3420 e, 

3421 ) 

3422 raise NetworkPolicyApplyError(rule_name, e.reason or str(e)) from e 

3423 

3424 def _create_deployment(self, name: str, namespace: str, spec: dict[str, Any]) -> None: 

3425 """Create a Kubernetes Deployment for an inference endpoint.""" 

3426 replicas = spec.get("replicas", 1) 

3427 deployment = self._build_inference_deployment_object( 

3428 name=name, 

3429 deploy_name=name, 

3430 app_label=name, 

3431 namespace=namespace, 

3432 spec=spec, 

3433 replicas=replicas, 

3434 ) 

3435 self._assert_mutation_authority() 

3436 self.apps_v1.create_namespaced_deployment( 

3437 namespace, deployment, _request_timeout=self._k8s_timeout 

3438 ) 

3439 self._confirm_created_resource( 

3440 kind="deployment", 

3441 resource_name=name, 

3442 read_resource=partial( 

3443 self.apps_v1.read_namespaced_deployment, 

3444 name, 

3445 namespace, 

3446 _request_timeout=self._k8s_timeout, 

3447 ), 

3448 delete_resource=partial( 

3449 self.apps_v1.delete_namespaced_deployment, 

3450 name, 

3451 namespace, 

3452 _request_timeout=self._k8s_timeout, 

3453 ), 

3454 ) 

3455 logger.info("Created deployment %s/%s", namespace, name) 

3456 

3457 def _build_inference_deployment_object( 

3458 self, 

3459 name: str, 

3460 deploy_name: str, 

3461 app_label: str, 

3462 namespace: str, 

3463 spec: dict[str, Any], 

3464 replicas: int, 

3465 extra_args: list[str] | None = None, 

3466 extra_labels: dict[str, str] | None = None, 

3467 ) -> client.V1Deployment: 

3468 """Build the ``V1Deployment`` object for an inference workload. 

3469 

3470 Shared by the single-Deployment path and the role-split prefill/decode/ 

3471 store paths. ``name`` is the endpoint name (used for the in-cluster 

3472 serving prefix and model cache directory), ``deploy_name`` is the 

3473 Kubernetes object name, and ``app_label`` is the selector label that 

3474 Services and autoscalers target. ``extra_args`` are appended to the 

3475 container args (for example the rendered ``--kv-transfer-config``), and 

3476 ``extra_labels`` are merged into both the Deployment and pod-template 

3477 labels so role pods carry a stable role marker. 

3478 """ 

3479 image = self._resolve_image_for_region(spec) 

3480 port = spec.get("port", 8000) 

3481 gpu_count = spec.get("gpu_count", 1) 

3482 health_path = spec.get("health_check_path", "/health") 

3483 env_vars = spec.get("env", {}) 

3484 # Stable block hashing across data-parallel ranks: identical prompts 

3485 # must hash identically so shared prefix-cache hits are not lost 

3486 # between pods. The disaggregated serving image (upstream vLLM) no 

3487 # longer bakes this in, so default it here; an explicit spec env wins. 

3488 env_vars = {"PYTHONHASHSEED": "0", **env_vars} 

3489 resources = spec.get("resources", {}) 

3490 model_path = spec.get("model_path") 

3491 command = spec.get("command") 

3492 args = spec.get("args") 

3493 

3494 # Build container 

3495 container_env = [client.V1EnvVar(name=k, value=str(v)) for k, v in env_vars.items()] 

3496 

3497 # Runtime behavior is persisted explicitly by callers that need a 

3498 # strict adapter. Legacy endpoints without the field retain vLLM image 

3499 # detection, but TGI is never given vLLM's unsupported --root-path: 

3500 # the authenticated platform proxy strips /inference/{name} before 

3501 # forwarding /health, /generate, or /info to the model Service. 

3502 serving_prefix = f"/inference/{name}" 

3503 runtime_framework = spec.get("framework") 

3504 if runtime_framework not in ("vllm", "tgi"): 

3505 image_lower = image.lower() 

3506 if "vllm" in image_lower: 

3507 runtime_framework = "vllm" 

3508 elif "text-generation-inference" in image_lower or "/tgi" in image_lower: 

3509 runtime_framework = "tgi" 

3510 else: 

3511 runtime_framework = None 

3512 if not command and runtime_framework == "vllm": 

3513 if args: 

3514 if "--root-path" not in args: 

3515 args = list(args) + ["--root-path", serving_prefix] 

3516 else: 

3517 args = ["--root-path", serving_prefix] 

3518 

3519 # Append caller-supplied arguments (for example the rendered 

3520 # --kv-transfer-config) after any root-path injection so they survive 

3521 # alongside user --extra-args. 

3522 if extra_args: 

3523 args = (list(args) if args else []) + list(extra_args) 

3524 

3525 resource_reqs = client.V1ResourceRequirements( 

3526 requests=resources.get("requests", {"cpu": "1", "memory": "4Gi"}), 

3527 limits=resources.get("limits", {"cpu": "4", "memory": "16Gi"}), 

3528 ) 

3529 # Add accelerator resources (GPU or Neuron) 

3530 accelerator = spec.get("accelerator", "nvidia") 

3531 if gpu_count > 0: 

3532 if accelerator == "neuron": 

3533 # AWS Trainium/Inferentia — request Neuron devices 

3534 if resource_reqs.limits is None: 

3535 resource_reqs.limits = {} 

3536 resource_reqs.limits["aws.amazon.com/neuron"] = str(gpu_count) 

3537 if resource_reqs.requests is None: 

3538 resource_reqs.requests = {} 

3539 resource_reqs.requests["aws.amazon.com/neuron"] = str(gpu_count) 

3540 else: 

3541 # NVIDIA GPU (default) 

3542 if resource_reqs.limits is None: 

3543 resource_reqs.limits = {} 

3544 resource_reqs.limits["nvidia.com/gpu"] = str(gpu_count) 

3545 if resource_reqs.requests is None: 

3546 resource_reqs.requests = {} 

3547 resource_reqs.requests["nvidia.com/gpu"] = str(gpu_count) 

3548 

3549 volume_mounts = [] 

3550 volumes = [] 

3551 init_containers = [] 

3552 model_source = spec.get("model_source") 

3553 

3554 if model_path or model_source: 

3555 volume_mounts.append( 

3556 client.V1VolumeMount( 

3557 name="model-storage", 

3558 mount_path="/models", 

3559 ) 

3560 ) 

3561 volumes.append( 

3562 client.V1Volume( 

3563 name="model-storage", 

3564 persistent_volume_claim=client.V1PersistentVolumeClaimVolumeSource( 

3565 claim_name="efs-claim", 

3566 ), 

3567 ) 

3568 ) 

3569 

3570 # Mooncake role pods read the shared transport config (metadata-server 

3571 # address, protocol, device) from the per-endpoint ``{name}-mooncake`` 

3572 # ConfigMap mounted read-only at MOONCAKE_CONFIG_MOUNT_DIR, pointed at by 

3573 # MOONCAKE_CONFIG_PATH, and learn the KV-transfer bootstrap base port via 

3574 # VLLM_MOONCAKE_BOOTSTRAP_PORT. Plain (non-mooncake) endpoints are 

3575 # untouched: no volume, mount, or env is added. 

3576 mooncake_block = spec.get("mooncake") 

3577 if mooncake_block: 

3578 volume_mounts.append( 

3579 client.V1VolumeMount( 

3580 name="mooncake-config", 

3581 mount_path=MOONCAKE_CONFIG_MOUNT_DIR, 

3582 read_only=True, 

3583 ) 

3584 ) 

3585 volumes.append( 

3586 client.V1Volume( 

3587 name="mooncake-config", 

3588 config_map=client.V1ConfigMapVolumeSource(name=f"{name}-mooncake"), 

3589 ) 

3590 ) 

3591 transfer_block = mooncake_block.get("transfer") or {} 

3592 base_port = transfer_block.get("bootstrap_base_port", MOONCAKE_BOOTSTRAP_BASE_PORT) 

3593 try: 

3594 base_port = int(base_port) 

3595 except TypeError, ValueError: 

3596 base_port = MOONCAKE_BOOTSTRAP_BASE_PORT 

3597 container_env.append( 

3598 client.V1EnvVar(name=MOONCAKE_CONFIG_PATH_ENV, value=MOONCAKE_CONFIG_FILE_PATH) 

3599 ) 

3600 container_env.append( 

3601 client.V1EnvVar(name=VLLM_MOONCAKE_BOOTSTRAP_PORT_ENV, value=str(base_port)) 

3602 ) 

3603 

3604 # Sync directly with literal argv so a model URI can never become 

3605 # shell syntax. ``aws s3 sync`` avoids retransferring unchanged 

3606 # objects on reruns while also repairing partial downloads. 

3607 if model_source and model_source.startswith("s3://"): 

3608 model_dest = f"/models/{name}" 

3609 init_containers.append( 

3610 client.V1Container( 

3611 name="model-sync", 

3612 image=AWS_CLI_IMAGE, 

3613 command=["aws"], 

3614 args=["s3", "sync", model_source, model_dest, "--quiet"], 

3615 volume_mounts=[ 

3616 client.V1VolumeMount( 

3617 name="model-storage", 

3618 mount_path="/models", 

3619 ) 

3620 ], 

3621 resources=client.V1ResourceRequirements( 

3622 requests={"cpu": "1", "memory": "2Gi"}, 

3623 limits={"cpu": "4", "memory": "8Gi"}, 

3624 ), 

3625 ) 

3626 ) 

3627 

3628 # Probe path depends on whether the server handles the prefix 

3629 uses_root_path = args is not None and "--root-path" in args 

3630 probe_health = f"{serving_prefix}{health_path}" if uses_root_path else health_path 

3631 

3632 container = client.V1Container( 

3633 name="inference", 

3634 image=image, 

3635 ports=[client.V1ContainerPort(container_port=port)], 

3636 env=container_env if container_env else None, 

3637 resources=resource_reqs, 

3638 volume_mounts=volume_mounts if volume_mounts else None, 

3639 command=command, 

3640 args=args, 

3641 startup_probe=( 

3642 client.V1Probe( 

3643 http_get=client.V1HTTPGetAction(path=health_path, port=port), 

3644 period_seconds=15, 

3645 failure_threshold=80, 

3646 ) 

3647 if runtime_framework == "tgi" 

3648 else None 

3649 ), 

3650 liveness_probe=client.V1Probe( 

3651 http_get=client.V1HTTPGetAction(path=probe_health, port=port), 

3652 initial_delay_seconds=120, 

3653 period_seconds=15, 

3654 failure_threshold=5, 

3655 ), 

3656 readiness_probe=client.V1Probe( 

3657 http_get=client.V1HTTPGetAction(path=probe_health, port=port), 

3658 initial_delay_seconds=30, 

3659 period_seconds=10, 

3660 ), 

3661 ) 

3662 

3663 # Build tolerations based on accelerator type 

3664 if accelerator == "neuron": 

3665 tolerations = [ 

3666 client.V1Toleration( 

3667 key="aws.amazon.com/neuron", 

3668 operator="Equal", 

3669 value="true", 

3670 effect="NoSchedule", 

3671 ) 

3672 ] 

3673 else: 

3674 tolerations = [ 

3675 client.V1Toleration( 

3676 key="nvidia.com/gpu", 

3677 operator="Equal", 

3678 value="true", 

3679 effect="NoSchedule", 

3680 ) 

3681 ] 

3682 

3683 # Node selector based on accelerator type 

3684 node_selector = spec.get("node_selector", {}) 

3685 if gpu_count > 0 and not node_selector: 

3686 if accelerator == "neuron": 

3687 node_selector = {"accelerator": "neuron"} 

3688 else: 

3689 node_selector = {"eks.amazonaws.com/instance-gpu-manufacturer": "nvidia"} 

3690 

3691 # Apply capacity type preference (spot/on-demand) 

3692 capacity_type = spec.get("capacity_type") 

3693 if capacity_type in ("spot", "on-demand"): 

3694 node_selector["karpenter.sh/capacity-type"] = capacity_type 

3695 

3696 labels = { 

3697 "app": app_label, 

3698 "project": "gco", 

3699 "gco.io/type": "inference", 

3700 } 

3701 if extra_labels: 

3702 labels.update(extra_labels) 

3703 

3704 deployment = client.V1Deployment( 

3705 metadata=client.V1ObjectMeta( 

3706 name=deploy_name, 

3707 namespace=namespace, 

3708 labels=dict(labels), 

3709 annotations=self._provenance_annotations(), 

3710 ), 

3711 spec=client.V1DeploymentSpec( 

3712 replicas=replicas, 

3713 selector=client.V1LabelSelector( 

3714 match_labels={"app": app_label}, 

3715 ), 

3716 template=client.V1PodTemplateSpec( 

3717 metadata=client.V1ObjectMeta( 

3718 labels=dict(labels), 

3719 annotations=self._provenance_annotations(), 

3720 ), 

3721 spec=client.V1PodSpec( 

3722 service_account_name="gco-service-account", 

3723 automount_service_account_token=False, 

3724 containers=[container], 

3725 init_containers=init_containers if init_containers else None, 

3726 tolerations=tolerations, 

3727 node_selector=node_selector if node_selector else None, 

3728 volumes=volumes if volumes else None, 

3729 ), 

3730 ), 

3731 ), 

3732 ) 

3733 

3734 return deployment 

3735 

3736 def _replica_count_for_role(self, mooncake: dict[str, Any], role: str) -> int: 

3737 """Resolve the materialized replica count for a role. 

3738 

3739 When per-role autoscaling is enabled and supplies a ``min_replicas`` 

3740 for the role, the role Deployment is materialized at that lower bound so 

3741 the autoscaler owns the count from there. Otherwise the count comes from 

3742 the topology: ``topology.prefill`` for prefill and ``topology.decode`` 

3743 for decode. The single store instance is always one replica. 

3744 """ 

3745 autoscaling = mooncake.get("autoscaling") or {} 

3746 if autoscaling.get("enabled") and role in ("prefill", "decode"): 

3747 role_cfg = autoscaling.get(role) or {} 

3748 min_replicas = role_cfg.get("min_replicas") 

3749 if isinstance(min_replicas, int) and not isinstance(min_replicas, bool): 

3750 return min_replicas 

3751 

3752 topology = mooncake.get("topology") or {} 

3753 if role == "prefill": 

3754 return int(topology.get("prefill", 1)) 

3755 if role == "decode": 

3756 return int(topology.get("decode", 1)) 

3757 # Single store instance: kv_both runs as one replica. 

3758 return 1 

3759 

3760 def _create_role_deployment(self, name: str, ns: str, spec: dict[str, Any], role: str) -> None: 

3761 """Materialize one role Deployment for a Mooncake endpoint. 

3762 

3763 Disaggregated and ``both`` modes split work across ``{name}-prefill`` 

3764 and ``{name}-decode``; store mode runs a single ``{name}`` instance with 

3765 the ``kv_both`` role. The role's ``--kv-transfer-config`` is attached to 

3766 the vLLM container, EFA scheduling is applied when transfer runs over 

3767 RDMA, and the replica count is taken from the topology (or the 

3768 autoscaling lower bound when that is enabled). 

3769 

3770 Args: 

3771 name: The endpoint name. 

3772 ns: The namespace to materialize into. 

3773 spec: The endpoint spec; ``spec["mooncake"]`` selects the mode and 

3774 topology. 

3775 role: One of ``"prefill"``, ``"decode"``, or ``"single"``. 

3776 """ 

3777 mooncake = spec.get("mooncake") or {} 

3778 

3779 # The store's single instance keeps the endpoint name; prefill and decode 

3780 # are suffixed so Services and autoscalers can target each role. 

3781 deploy_name = name if role == "single" else f"{name}-{role}" 

3782 

3783 kv_transfer_config = build_kv_transfer_config(mooncake, role) 

3784 replicas = self._replica_count_for_role(mooncake, role) 

3785 

3786 deployment = self._build_inference_deployment_object( 

3787 name=name, 

3788 deploy_name=deploy_name, 

3789 app_label=deploy_name, 

3790 namespace=ns, 

3791 spec=spec, 

3792 replicas=replicas, 

3793 extra_args=["--kv-transfer-config", kv_transfer_config], 

3794 extra_labels={"gco.io/role": role}, 

3795 ) 

3796 

3797 # Land role pods on the EFA fabric when KV transfer runs over RDMA, 

3798 # preserving the GPU asks already built into the pod. 

3799 apply_efa_scheduling(mooncake, deployment.spec.template.spec) 

3800 

3801 self._assert_mutation_authority() 

3802 self.apps_v1.create_namespaced_deployment( 

3803 ns, deployment, _request_timeout=self._k8s_timeout 

3804 ) 

3805 self._confirm_created_resource( 

3806 kind="deployment", 

3807 resource_name=deploy_name, 

3808 read_resource=partial( 

3809 self.apps_v1.read_namespaced_deployment, 

3810 deploy_name, 

3811 ns, 

3812 _request_timeout=self._k8s_timeout, 

3813 ), 

3814 delete_resource=partial( 

3815 self.apps_v1.delete_namespaced_deployment, 

3816 deploy_name, 

3817 ns, 

3818 _request_timeout=self._k8s_timeout, 

3819 ), 

3820 ) 

3821 logger.info("Created role deployment %s/%s (role=%s)", ns, deploy_name, role) 

3822 

3823 def _verify_admin_api_key_secret(self, proxy: dict[str, Any], ns: str) -> str: 

3824 """Confirm the proxy admin key Secret exists and carries a key value. 

3825 

3826 The proxy guards a privileged admin path and must never run without a 

3827 usable ``ADMIN_API_KEY``. This reads the Secret named by 

3828 ``proxy.admin_api_key_secret`` and confirms it holds a non-empty 

3829 ``ADMIN_API_KEY`` value, so the value itself never has to be carried on 

3830 the spec or a command argument. 

3831 

3832 Args: 

3833 proxy: The ``spec["mooncake"]["proxy"]`` block; ``admin_api_key_secret`` 

3834 names the backing Secret. 

3835 ns: The namespace the Secret lives in. 

3836 

3837 Returns: 

3838 The verified Secret name, suitable for a Secret reference. 

3839 

3840 Raises: 

3841 AdminApiKeySecretError: If the spec names no Secret, the Secret is 

3842 absent, or its ``ADMIN_API_KEY`` value is empty or missing. 

3843 """ 

3844 secret_name = proxy.get("admin_api_key_secret") 

3845 if not isinstance(secret_name, str) or not secret_name: 

3846 raise AdminApiKeySecretError(None, "no admin API key Secret was named") 

3847 

3848 try: 

3849 secret = self.core_v1.read_namespaced_secret( 

3850 secret_name, ns, _request_timeout=self._k8s_timeout 

3851 ) 

3852 except ApiException as e: 

3853 if e.status == 404: 

3854 raise AdminApiKeySecretError(secret_name, "Secret not found") from e 

3855 raise 

3856 

3857 if not self._secret_has_admin_api_key(secret): 

3858 raise AdminApiKeySecretError( 

3859 secret_name, 

3860 f"{ADMIN_API_KEY_SECRET_DATA_KEY} value is empty or missing", 

3861 ) 

3862 

3863 return secret_name 

3864 

3865 @staticmethod 

3866 def _secret_has_admin_api_key(secret: client.V1Secret) -> bool: 

3867 """Return whether ``secret`` carries a non-empty ``ADMIN_API_KEY``. 

3868 

3869 Both the base64 ``data`` and the plaintext ``string_data`` views are 

3870 considered, and a value is treated as present only when it decodes to a 

3871 non-empty string. 

3872 """ 

3873 string_data = secret.string_data or {} 

3874 plain = string_data.get(ADMIN_API_KEY_SECRET_DATA_KEY) 

3875 if plain: 

3876 return True 

3877 

3878 data = secret.data or {} 

3879 encoded = data.get(ADMIN_API_KEY_SECRET_DATA_KEY) 

3880 if not encoded: 

3881 return False 

3882 try: 

3883 return bool(base64.b64decode(encoded)) 

3884 except ValueError, TypeError: 

3885 # A value that cannot be decoded is unusable as an admin key. 

3886 return False 

3887 

3888 def _ensure_admin_api_key_secret( 

3889 self, 

3890 name: str, 

3891 proxy: dict[str, Any], 

3892 ns: str, 

3893 lifecycle_id: str, 

3894 ) -> str: 

3895 """Return the proxy admin-key Secret name, provisioning one if needed. 

3896 

3897 The prefill-decode proxy guards a privileged admin path and must never 

3898 run without a usable ``ADMIN_API_KEY``. Two paths satisfy that: 

3899 

3900 - **Bring-your-own**: when the proxy block names a Secret, that Secret 

3901 must already exist and carry a non-empty ``ADMIN_API_KEY``; otherwise 

3902 the deployment is rejected, so a typo or a missing pre-created Secret 

3903 fails fast. The named Secret is only read, never created or mutated. 

3904 - **Auto-managed**: when the proxy names no Secret, a per-endpoint 

3905 ``{name}-admin`` Secret is provisioned create-if-absent with a 

3906 generated key, so a split deploy needs no manual Secret. The generated 

3907 key only ever lives in the cluster — it is never written to the 

3908 endpoint spec, a command argument, or a log line. 

3909 

3910 Args: 

3911 name: The endpoint name, used to derive the auto-managed Secret name. 

3912 proxy: The ``spec["mooncake"]["proxy"]`` block. 

3913 ns: The namespace the Secret lives in. 

3914 

3915 Returns: 

3916 The Secret name to reference from the proxy container. 

3917 

3918 Raises: 

3919 AdminApiKeySecretError: Only on the bring-your-own path, when the 

3920 named Secret is absent or its ``ADMIN_API_KEY`` is empty. The 

3921 auto-managed path never raises this. 

3922 """ 

3923 named = proxy.get("admin_api_key_secret") 

3924 if isinstance(named, str) and named: 

3925 return self._verify_admin_api_key_secret(proxy, ns) 

3926 return self._provision_admin_api_key_secret(f"{name}-admin", ns, lifecycle_id) 

3927 

3928 def _provision_admin_api_key_secret( 

3929 self, 

3930 secret_name: str, 

3931 ns: str, 

3932 lifecycle_id: str, 

3933 ) -> str: 

3934 """Create the auto-managed proxy admin-key Secret if absent. 

3935 

3936 Uses create-if-absent semantics so the key stays stable across reconcile 

3937 passes: an existing Secret (the steady state, or one a prior pass 

3938 created) is left untouched, and a concurrent create (409) is treated as 

3939 success. A freshly created Secret carries a cryptographically strong 

3940 64-character hex ``ADMIN_API_KEY`` from :func:`secrets.token_hex`, which 

3941 reaches the proxy only through a Secret reference — the value is never 

3942 logged or written to the spec. 

3943 

3944 Args: 

3945 secret_name: The Secret to ensure exists (``{endpoint}-admin``). 

3946 ns: The namespace to create it in. 

3947 

3948 Returns: 

3949 The Secret name, ready for a Secret reference. 

3950 """ 

3951 try: 

3952 existing = self.core_v1.read_namespaced_secret( 

3953 secret_name, 

3954 ns, 

3955 _request_timeout=self._k8s_timeout, 

3956 ) 

3957 except ApiException as e: 

3958 if e.status != 404: 

3959 raise 

3960 else: 

3961 existing = self._authorize_resource( 

3962 existing, 

3963 kind="secret", 

3964 resource_name=secret_name, 

3965 patch_metadata=partial( 

3966 self.core_v1.patch_namespaced_secret, 

3967 secret_name, 

3968 ns, 

3969 _request_timeout=self._k8s_timeout, 

3970 ), 

3971 read_resource=lambda: self.core_v1.read_namespaced_secret( 

3972 secret_name, ns, _request_timeout=self._k8s_timeout 

3973 ), 

3974 delete_resource=partial( 

3975 self.core_v1.delete_namespaced_secret, 

3976 secret_name, 

3977 ns, 

3978 _request_timeout=self._k8s_timeout, 

3979 ), 

3980 ) 

3981 self._require_owned_admin_secret( 

3982 existing, 

3983 secret_name, 

3984 ns, 

3985 lifecycle_id, 

3986 ) 

3987 return secret_name 

3988 

3989 ownership = self._generated_admin_secret_labels(secret_name) 

3990 annotations = self._provenance_annotations() or {_LIFECYCLE_ANNOTATION: lifecycle_id} 

3991 secret = client.V1Secret( 

3992 metadata=client.V1ObjectMeta( 

3993 name=secret_name, 

3994 namespace=ns, 

3995 labels=ownership, 

3996 annotations=annotations, 

3997 ), 

3998 string_data={ADMIN_API_KEY_SECRET_DATA_KEY: secrets.token_hex(32)}, 

3999 type="Opaque", 

4000 ) 

4001 # The two logger.info calls below carry a bare `# nosemgrep`: the 

4002 # logger-credential-disclosure rule matches the literal word "Secret" in 

4003 # the message, but only the Secret's name and namespace (%s/%s) are 

4004 # logged here — never the generated key value set above in string_data. 

4005 self._assert_mutation_authority() 

4006 try: 

4007 self.core_v1.create_namespaced_secret(ns, secret, _request_timeout=self._k8s_timeout) 

4008 self._confirm_created_resource( 

4009 kind="secret", 

4010 resource_name=secret_name, 

4011 read_resource=partial( 

4012 self.core_v1.read_namespaced_secret, 

4013 secret_name, 

4014 ns, 

4015 _request_timeout=self._k8s_timeout, 

4016 ), 

4017 delete_resource=partial( 

4018 self.core_v1.delete_namespaced_secret, 

4019 secret_name, 

4020 ns, 

4021 _request_timeout=self._k8s_timeout, 

4022 ), 

4023 ) 

4024 logger.info("Provisioned proxy admin-key Secret %s/%s", ns, secret_name) # nosemgrep 

4025 except ApiException as e: 

4026 if e.status != 409: 

4027 raise 

4028 existing = self.core_v1.read_namespaced_secret( 

4029 secret_name, 

4030 ns, 

4031 _request_timeout=self._k8s_timeout, 

4032 ) 

4033 existing = self._authorize_resource( 

4034 existing, 

4035 kind="secret", 

4036 resource_name=secret_name, 

4037 patch_metadata=partial( 

4038 self.core_v1.patch_namespaced_secret, 

4039 secret_name, 

4040 ns, 

4041 _request_timeout=self._k8s_timeout, 

4042 ), 

4043 read_resource=lambda: self.core_v1.read_namespaced_secret( 

4044 secret_name, ns, _request_timeout=self._k8s_timeout 

4045 ), 

4046 delete_resource=partial( 

4047 self.core_v1.delete_namespaced_secret, 

4048 secret_name, 

4049 ns, 

4050 _request_timeout=self._k8s_timeout, 

4051 ), 

4052 ) 

4053 try: 

4054 self._require_owned_admin_secret( 

4055 existing, 

4056 secret_name, 

4057 ns, 

4058 lifecycle_id, 

4059 ) 

4060 except AdminApiKeySecretError as error: 

4061 raise AdminApiKeySecretError( 

4062 secret_name, 

4063 "a concurrent conventional Secret lacks matching lifecycle provenance", 

4064 ) from error 

4065 logger.info("Proxy admin-key Secret %s/%s exists", ns, secret_name) # nosemgrep 

4066 return secret_name 

4067 

4068 def _create_pd_proxy( 

4069 self, name: str, ns: str, spec: dict[str, Any], endpoint: dict[str, Any] 

4070 ) -> None: 

4071 """Materialize the prefill-decode proxy front for a disaggregated endpoint. 

4072 

4073 Disaggregated and ``both`` modes are fronted by a lightweight proxy that 

4074 runs the residency check and dispatches each request to the prefill and 

4075 decode pods. It materializes a ConfigMap, a proxy Deployment with at 

4076 least one replica, and a Service whose selector matches only the proxy 

4077 pods. The shared HTTPRoute attached to ``gco-system/gco-gateway`` sends 

4078 ``/inference`` to ``gco-system/inference-proxy``; that authenticated 

4079 platform proxy then reaches this internal ClusterIP Service. 

4080 Endpoint-specific Ingresses are removed as an unsafe legacy path. 

4081 

4082 Before those resources are created, a user-named 

4083 ``mooncake.proxy.admin_api_key_secret`` is verified to contain a usable 

4084 ``ADMIN_API_KEY``. When no Secret is named, the monitor auto-provisions 

4085 a generated ``{name}-admin`` Secret instead. A missing or empty named 

4086 Secret rejects the proxy; the key itself reaches the container only as 

4087 a Secret reference at pod start and is never written to the spec or a 

4088 command argument. 

4089 

4090 Creation is idempotent at the API boundary: an already-present Deployment 

4091 or ClusterIP Service is left in place, and historical direct Ingresses are 

4092 deleted if present. No endpoint Gateway or HTTPRoute is created. 

4093 

4094 Args: 

4095 name: The endpoint name. 

4096 ns: The namespace to materialize into. 

4097 spec: The endpoint spec; ``spec["mooncake"]`` supplies the proxy 

4098 image and behavior. 

4099 endpoint: The endpoint record. Legacy per-endpoint routing metadata 

4100 is ignored because the shared platform HTTPRoute owns the prefix. 

4101 

4102 Raises: 

4103 AdminApiKeySecretError: If the admin key Secret is missing, names no 

4104 Secret, or holds an empty ``ADMIN_API_KEY``. No proxy resource 

4105 is created in that case. 

4106 """ 

4107 mooncake = spec.get("mooncake") or {} 

4108 proxy = mooncake.get("proxy") or {} 

4109 proxy_name = f"{name}-proxy" 

4110 lifecycle_id = endpoint.get("lifecycle_id") 

4111 if not isinstance(lifecycle_id, str) or not lifecycle_id: 

4112 raise AdminApiKeySecretError( 

4113 None, 

4114 "endpoint record has no immutable lifecycle identity", 

4115 ) 

4116 

4117 # The proxy fronts a privileged admin path, so it never starts without a 

4118 # usable admin key. When the spec names a Secret it must already exist 

4119 # and be non-empty (the deployment is rejected otherwise); when it names 

4120 # none, a per-endpoint admin-key Secret is auto-provisioned with a 

4121 # generated key. Either way the key reaches the container only by Secret 

4122 # reference. 

4123 admin_secret_name = self._ensure_admin_api_key_secret( 

4124 name, 

4125 proxy, 

4126 ns, 

4127 lifecycle_id, 

4128 ) 

4129 

4130 proxy_env = build_pd_proxy_config(mooncake) 

4131 container_env = [client.V1EnvVar(name=k, value=v) for k, v in proxy_env.items()] 

4132 # Deliver the admin key by Secret reference only — its value is never 

4133 # placed on the spec or a command argument. 

4134 container_env.append( 

4135 client.V1EnvVar( 

4136 name=PD_PROXY_ADMIN_API_KEY_ENV, 

4137 value_from=client.V1EnvVarSource( 

4138 secret_key_ref=client.V1SecretKeySelector( 

4139 name=admin_secret_name, 

4140 key=ADMIN_API_KEY_SECRET_DATA_KEY, 

4141 ) 

4142 ), 

4143 ) 

4144 ) 

4145 

4146 # The proxy fronts at least one replica; a spec may ask for more. 

4147 replicas = proxy.get("replicas", 1) 

4148 if not isinstance(replicas, int) or isinstance(replicas, bool) or replicas < 1: 

4149 replicas = 1 

4150 

4151 labels = { 

4152 "app": proxy_name, 

4153 "project": "gco", 

4154 "gco.io/type": "inference", 

4155 "gco.io/role": PD_PROXY_ROLE_LABEL, 

4156 } 

4157 

4158 # The proxy reaches prefill and decode through their per-role Services 

4159 # and listens on PD_PROXY_PORT for requests from the authenticated API 

4160 # proxy. Routing via Services means only Ready role pods receive traffic. 

4161 port = spec.get("port", 8000) 

4162 container_env.extend( 

4163 [ 

4164 client.V1EnvVar(name=PD_PROXY_PORT_ENV, value=str(PD_PROXY_PORT)), 

4165 client.V1EnvVar( 

4166 name=PD_PROXY_PREFILL_URL_ENV, value=f"http://{name}-prefill:{port}" 

4167 ), 

4168 client.V1EnvVar(name=PD_PROXY_DECODE_URL_ENV, value=f"http://{name}-decode:{port}"), 

4169 ] 

4170 ) 

4171 

4172 # Ship the proxy program to the pod as a ConfigMap and run it from there. 

4173 self._ensure_pd_proxy_configmap(name, ns) 

4174 proxy_volume_name = "pd-proxy-script" 

4175 

4176 container = client.V1Container( 

4177 name="proxy", 

4178 image=proxy.get("image"), 

4179 command=["python3", PD_PROXY_SCRIPT_PATH], 

4180 ports=[client.V1ContainerPort(container_port=PD_PROXY_PORT)], 

4181 env=container_env if container_env else None, 

4182 resources=client.V1ResourceRequirements( 

4183 requests={"cpu": "250m", "memory": "256Mi"}, 

4184 limits={"cpu": "1", "memory": "1Gi"}, 

4185 ), 

4186 volume_mounts=[ 

4187 client.V1VolumeMount( 

4188 name=proxy_volume_name, 

4189 mount_path=PD_PROXY_CONFIG_MOUNT_DIR, 

4190 read_only=True, 

4191 ) 

4192 ], 

4193 readiness_probe=client.V1Probe( 

4194 tcp_socket=client.V1TCPSocketAction(port=PD_PROXY_PORT), 

4195 initial_delay_seconds=10, 

4196 period_seconds=10, 

4197 ), 

4198 liveness_probe=client.V1Probe( 

4199 tcp_socket=client.V1TCPSocketAction(port=PD_PROXY_PORT), 

4200 initial_delay_seconds=30, 

4201 period_seconds=15, 

4202 failure_threshold=5, 

4203 ), 

4204 ) 

4205 

4206 deployment = client.V1Deployment( 

4207 metadata=client.V1ObjectMeta( 

4208 name=proxy_name, 

4209 namespace=ns, 

4210 labels=dict(labels), 

4211 annotations=self._provenance_annotations(), 

4212 ), 

4213 spec=client.V1DeploymentSpec( 

4214 replicas=replicas, 

4215 selector=client.V1LabelSelector(match_labels={"app": proxy_name}), 

4216 template=client.V1PodTemplateSpec( 

4217 metadata=client.V1ObjectMeta( 

4218 labels=dict(labels), 

4219 annotations=self._provenance_annotations(), 

4220 ), 

4221 spec=client.V1PodSpec( 

4222 service_account_name="gco-service-account", 

4223 automount_service_account_token=False, 

4224 containers=[container], 

4225 volumes=[ 

4226 client.V1Volume( 

4227 name=proxy_volume_name, 

4228 config_map=client.V1ConfigMapVolumeSource( 

4229 name=f"{name}-pd-proxy", 

4230 default_mode=0o555, 

4231 ), 

4232 ) 

4233 ], 

4234 ), 

4235 ), 

4236 ), 

4237 ) 

4238 

4239 self._assert_mutation_authority() 

4240 try: 

4241 self.apps_v1.create_namespaced_deployment( 

4242 ns, deployment, _request_timeout=self._k8s_timeout 

4243 ) 

4244 self._confirm_created_resource( 

4245 kind="deployment", 

4246 resource_name=proxy_name, 

4247 read_resource=partial( 

4248 self.apps_v1.read_namespaced_deployment, 

4249 proxy_name, 

4250 ns, 

4251 _request_timeout=self._k8s_timeout, 

4252 ), 

4253 delete_resource=partial( 

4254 self.apps_v1.delete_namespaced_deployment, 

4255 proxy_name, 

4256 ns, 

4257 _request_timeout=self._k8s_timeout, 

4258 ), 

4259 ) 

4260 logger.info("Created proxy deployment %s/%s", ns, proxy_name) 

4261 except ApiException as error: 

4262 if error.status != 409: 

4263 raise 

4264 existing = self._get_deployment(proxy_name, ns) 

4265 if existing is None: 

4266 raise ReconcileFencedError( 

4267 "proxy deployment disappeared during reconciliation" 

4268 ) from error 

4269 _metadata, _annotations, _uid, resource_version = self._object_metadata(existing) 

4270 deployment.metadata.resource_version = resource_version 

4271 self._assert_mutation_authority() 

4272 self.apps_v1.patch_namespaced_deployment( 

4273 proxy_name, 

4274 ns, 

4275 body=deployment, 

4276 _request_timeout=self._k8s_timeout, 

4277 ) 

4278 logger.info("Reconciled proxy deployment %s/%s", ns, proxy_name) 

4279 

4280 self._create_proxy_service(proxy_name, ns) 

4281 

4282 def _authorize_existing_service(self, name: str, namespace: str) -> Any: 

4283 service = self.core_v1.read_namespaced_service( 

4284 name, namespace, _request_timeout=self._k8s_timeout 

4285 ) 

4286 return self._authorize_resource( 

4287 service, 

4288 kind="service", 

4289 resource_name=name, 

4290 patch_metadata=partial( 

4291 self.core_v1.patch_namespaced_service, 

4292 name, 

4293 namespace, 

4294 _request_timeout=self._k8s_timeout, 

4295 ), 

4296 read_resource=lambda: self.core_v1.read_namespaced_service( 

4297 name, namespace, _request_timeout=self._k8s_timeout 

4298 ), 

4299 delete_resource=partial( 

4300 self.core_v1.delete_namespaced_service, 

4301 name, 

4302 namespace, 

4303 _request_timeout=self._k8s_timeout, 

4304 ), 

4305 ) 

4306 

4307 def _create_role_service(self, name: str, ns: str, role: str, port: int = 8000) -> None: 

4308 """Create the ClusterIP Service that fronts one role's pods. 

4309 

4310 Named ``{name}-{role}`` and selecting that role Deployment's app label, 

4311 so the PD proxy can address prefill or decode by stable in-cluster DNS. 

4312 Routing through a Service means kube-proxy load-balances across only the 

4313 role's Ready pods, which is what gives the proxy ready-only decode 

4314 routing without watching the Kubernetes API. Idempotent at the API 

4315 boundary: an already-present Service is left in place. 

4316 """ 

4317 deploy_name = f"{name}-{role}" 

4318 service = client.V1Service( 

4319 metadata=client.V1ObjectMeta( 

4320 name=deploy_name, 

4321 namespace=ns, 

4322 labels={ 

4323 "app": deploy_name, 

4324 "project": "gco", 

4325 "gco.io/type": "inference", 

4326 "gco.io/role": role, 

4327 }, 

4328 annotations=self._provenance_annotations(), 

4329 ), 

4330 spec=client.V1ServiceSpec( 

4331 selector=_inference_service_selector(deploy_name), 

4332 ports=[client.V1ServicePort(port=port, target_port=port, protocol="TCP")], 

4333 type="ClusterIP", 

4334 ), 

4335 ) 

4336 self._assert_mutation_authority() 

4337 try: 

4338 self.core_v1.create_namespaced_service(ns, service, _request_timeout=self._k8s_timeout) 

4339 self._confirm_created_resource( 

4340 kind="service", 

4341 resource_name=deploy_name, 

4342 read_resource=partial( 

4343 self.core_v1.read_namespaced_service, 

4344 deploy_name, 

4345 ns, 

4346 _request_timeout=self._k8s_timeout, 

4347 ), 

4348 delete_resource=partial( 

4349 self.core_v1.delete_namespaced_service, 

4350 deploy_name, 

4351 ns, 

4352 _request_timeout=self._k8s_timeout, 

4353 ), 

4354 ) 

4355 logger.info("Created role service %s/%s", ns, deploy_name) 

4356 except ApiException as error: 

4357 if error.status != 409: 

4358 raise 

4359 self._authorize_existing_service(deploy_name, ns) 

4360 logger.info("Role service %s/%s already exists", ns, deploy_name) 

4361 

4362 def _ensure_pd_proxy_configmap(self, name: str, ns: str) -> None: 

4363 """Publish the PD proxy program to the pod as a ConfigMap. 

4364 

4365 The proxy program (``mooncake_pd_proxy.py``) ships in this image 

4366 alongside the monitor; its source is read here and mounted into the 

4367 ``{name}-proxy`` pod, which runs it with ``python3`` from 

4368 ``PD_PROXY_SCRIPT_PATH``. The ConfigMap is patched on conflict so the 

4369 program tracks the running monitor build. 

4370 """ 

4371 script = (Path(__file__).resolve().parent / PD_PROXY_SCRIPT_FILENAME).read_text( 

4372 encoding="utf-8" 

4373 ) 

4374 cm_name = f"{name}-pd-proxy" 

4375 body = client.V1ConfigMap( 

4376 metadata=client.V1ObjectMeta( 

4377 name=cm_name, 

4378 namespace=ns, 

4379 labels={ 

4380 "app": f"{name}-proxy", 

4381 "project": "gco", 

4382 "gco.io/type": "inference", 

4383 "gco.io/role": PD_PROXY_ROLE_LABEL, 

4384 }, 

4385 annotations=self._provenance_annotations(), 

4386 ), 

4387 data={PD_PROXY_SCRIPT_FILENAME: script}, 

4388 ) 

4389 self._assert_mutation_authority() 

4390 try: 

4391 self.core_v1.create_namespaced_config_map(ns, body, _request_timeout=self._k8s_timeout) 

4392 self._confirm_created_resource( 

4393 kind="configmap", 

4394 resource_name=cm_name, 

4395 read_resource=partial( 

4396 self.core_v1.read_namespaced_config_map, 

4397 cm_name, 

4398 ns, 

4399 _request_timeout=self._k8s_timeout, 

4400 ), 

4401 delete_resource=partial( 

4402 self.core_v1.delete_namespaced_config_map, 

4403 cm_name, 

4404 ns, 

4405 _request_timeout=self._k8s_timeout, 

4406 ), 

4407 ) 

4408 logger.info("Created PD proxy ConfigMap %s/%s", ns, cm_name) 

4409 except ApiException as error: 

4410 if error.status != 409: 

4411 raise 

4412 existing = self.core_v1.read_namespaced_config_map( 

4413 cm_name, ns, _request_timeout=self._k8s_timeout 

4414 ) 

4415 existing = self._authorize_resource( 

4416 existing, 

4417 kind="configmap", 

4418 resource_name=cm_name, 

4419 patch_metadata=partial( 

4420 self.core_v1.patch_namespaced_config_map, 

4421 cm_name, 

4422 ns, 

4423 _request_timeout=self._k8s_timeout, 

4424 ), 

4425 read_resource=lambda: self.core_v1.read_namespaced_config_map( 

4426 cm_name, ns, _request_timeout=self._k8s_timeout 

4427 ), 

4428 delete_resource=partial( 

4429 self.core_v1.delete_namespaced_config_map, 

4430 cm_name, 

4431 ns, 

4432 _request_timeout=self._k8s_timeout, 

4433 ), 

4434 ) 

4435 _metadata, _annotations, _uid, resource_version = self._object_metadata(existing) 

4436 body.metadata.resource_version = resource_version 

4437 self._assert_mutation_authority() 

4438 self.core_v1.patch_namespaced_config_map( 

4439 cm_name, ns, body, _request_timeout=self._k8s_timeout 

4440 ) 

4441 logger.info("Updated PD proxy ConfigMap %s/%s", ns, cm_name) 

4442 

4443 def _create_proxy_service(self, proxy_name: str, namespace: str) -> None: 

4444 """Create the Service that fronts only the proxy pods. 

4445 

4446 The selector is the ``{name}-proxy`` app label together with the proxy 

4447 role marker, so the Service resolves exclusively to proxy pods and never 

4448 to the prefill or decode role pods that share the namespace. 

4449 """ 

4450 service = client.V1Service( 

4451 metadata=client.V1ObjectMeta( 

4452 name=proxy_name, 

4453 namespace=namespace, 

4454 labels={ 

4455 "app": proxy_name, 

4456 "project": "gco", 

4457 "gco.io/type": "inference", 

4458 "gco.io/role": PD_PROXY_ROLE_LABEL, 

4459 }, 

4460 annotations=self._provenance_annotations(), 

4461 ), 

4462 spec=client.V1ServiceSpec( 

4463 selector=_inference_service_selector( 

4464 proxy_name, **{"gco.io/role": PD_PROXY_ROLE_LABEL} 

4465 ), 

4466 ports=[ 

4467 client.V1ServicePort( 

4468 port=80, 

4469 target_port=PD_PROXY_PORT, 

4470 protocol="TCP", 

4471 ) 

4472 ], 

4473 type="ClusterIP", 

4474 ), 

4475 ) 

4476 

4477 self._assert_mutation_authority() 

4478 try: 

4479 self.core_v1.create_namespaced_service( 

4480 namespace, service, _request_timeout=self._k8s_timeout 

4481 ) 

4482 self._confirm_created_resource( 

4483 kind="service", 

4484 resource_name=proxy_name, 

4485 read_resource=partial( 

4486 self.core_v1.read_namespaced_service, 

4487 proxy_name, 

4488 namespace, 

4489 _request_timeout=self._k8s_timeout, 

4490 ), 

4491 delete_resource=partial( 

4492 self.core_v1.delete_namespaced_service, 

4493 proxy_name, 

4494 namespace, 

4495 _request_timeout=self._k8s_timeout, 

4496 ), 

4497 ) 

4498 logger.info("Created proxy service %s/%s", namespace, proxy_name) 

4499 except ApiException as error: 

4500 if error.status != 409: 

4501 raise 

4502 self._authorize_existing_service(proxy_name, namespace) 

4503 logger.info("Proxy service %s/%s already exists", namespace, proxy_name) 

4504 

4505 def _create_service(self, name: str, namespace: str, spec: dict[str, Any]) -> None: 

4506 """Create the internal ClusterIP Service for an inference endpoint.""" 

4507 port = spec.get("port", 8000) 

4508 

4509 service = client.V1Service( 

4510 metadata=client.V1ObjectMeta( 

4511 name=name, 

4512 namespace=namespace, 

4513 labels={ 

4514 "app": name, 

4515 "project": "gco", 

4516 "gco.io/type": "inference", 

4517 }, 

4518 annotations=self._provenance_annotations(), 

4519 ), 

4520 spec=client.V1ServiceSpec( 

4521 selector=_inference_service_selector(name), 

4522 ports=[ 

4523 client.V1ServicePort( 

4524 port=80, 

4525 target_port=port, 

4526 protocol="TCP", 

4527 ) 

4528 ], 

4529 type="ClusterIP", 

4530 ), 

4531 ) 

4532 

4533 self._assert_mutation_authority() 

4534 try: 

4535 self.core_v1.create_namespaced_service( 

4536 namespace, service, _request_timeout=self._k8s_timeout 

4537 ) 

4538 self._confirm_created_resource( 

4539 kind="service", 

4540 resource_name=name, 

4541 read_resource=partial( 

4542 self.core_v1.read_namespaced_service, 

4543 name, 

4544 namespace, 

4545 _request_timeout=self._k8s_timeout, 

4546 ), 

4547 delete_resource=partial( 

4548 self.core_v1.delete_namespaced_service, 

4549 name, 

4550 namespace, 

4551 _request_timeout=self._k8s_timeout, 

4552 ), 

4553 ) 

4554 logger.info("Created service %s/%s", namespace, name) 

4555 except ApiException as error: 

4556 if error.status != 409: 

4557 raise 

4558 self._authorize_existing_service(name, namespace) 

4559 logger.info("Service %s/%s already exists", namespace, name) 

4560 

4561 def _ensure_service(self, name: str, namespace: str, spec: dict[str, Any]) -> None: 

4562 """Ensure an owned endpoint Service exists, recreating it if absent.""" 

4563 try: 

4564 self._authorize_existing_service(name, namespace) 

4565 except ApiException as error: 

4566 if error.status == 404: 

4567 logger.warning("Service %s/%s missing, recreating", namespace, name) 

4568 self._create_service(name, namespace, spec) 

4569 else: 

4570 raise 

4571 

4572 def _check_health_watchdog( 

4573 self, 

4574 name: str, 

4575 namespace: str, 

4576 ready_replicas: int, 

4577 desired_replicas: int, 

4578 spec: dict[str, Any], 

4579 endpoint: dict[str, Any], 

4580 ) -> bool: 

4581 """Track prolonged unavailability without changing shared routing. 

4582 

4583 ``gco-system/gco-gateway`` owns the shared ``/inference`` HTTPRoute to 

4584 ``gco-system/inference-proxy``. Individual models are reached through 

4585 internal ClusterIP Services, so their readiness never changes the shared 

4586 Gateway or HTTPRoute. The threshold still drives degraded-state logging. 

4587 """ 

4588 del namespace, spec, endpoint 

4589 if ready_replicas > 0: 

4590 if name in self._unready_since: 

4591 logger.info("Endpoint %s recovered", name) 

4592 del self._unready_since[name] 

4593 return False 

4594 

4595 now = datetime.now(UTC) 

4596 if name not in self._unready_since: 

4597 self._unready_since[name] = now 

4598 logger.warning( 

4599 "Endpoint %s has 0/%d ready replicas, starting health watchdog timer", 

4600 name, 

4601 desired_replicas, 

4602 ) 

4603 return False 

4604 

4605 unready_duration = (now - self._unready_since[name]).total_seconds() 

4606 threshold_exceeded = unready_duration >= self._unhealthy_threshold_seconds 

4607 if threshold_exceeded: 

4608 logger.warning( 

4609 "WATCHDOG: Endpoint %s has been unavailable for %ds (threshold %ds); " 

4610 "the authenticated proxy will return 503 until it recovers", 

4611 name, 

4612 int(unready_duration), 

4613 self._unhealthy_threshold_seconds, 

4614 ) 

4615 return threshold_exceeded 

4616 

4617 def _scale_deployment(self, name: str, namespace: str, replicas: int) -> None: 

4618 """Scale only the exact authorized Deployment resourceVersion.""" 

4619 deployment = self._get_deployment(name, namespace) 

4620 if deployment is None: 

4621 raise ReconcileFencedError(f"deployment {name} disappeared before scaling") 

4622 _metadata, _annotations, _uid, resource_version = self._object_metadata(deployment) 

4623 self._assert_mutation_authority() 

4624 body: dict[str, Any] = {"spec": {"replicas": replicas}} 

4625 if resource_version: 

4626 body["metadata"] = {"resourceVersion": resource_version} 

4627 self.apps_v1.patch_namespaced_deployment( 

4628 name, 

4629 namespace, 

4630 body=body, 

4631 _request_timeout=self._k8s_timeout, 

4632 ) 

4633 

4634 def _update_deployment_image(self, name: str, namespace: str, image: str) -> None: 

4635 """Update only the exact authorized Deployment resourceVersion.""" 

4636 deployment = self._get_deployment(name, namespace) 

4637 if deployment is None: 

4638 raise ReconcileFencedError(f"deployment {name} disappeared before image update") 

4639 _metadata, _annotations, _uid, resource_version = self._object_metadata(deployment) 

4640 self._assert_mutation_authority() 

4641 body: dict[str, Any] = { 

4642 "spec": {"template": {"spec": {"containers": [{"name": "inference", "image": image}]}}} 

4643 } 

4644 if resource_version: 

4645 body["metadata"] = {"resourceVersion": resource_version} 

4646 self.apps_v1.patch_namespaced_deployment( 

4647 name, 

4648 namespace, 

4649 body=body, 

4650 _request_timeout=self._k8s_timeout, 

4651 ) 

4652 

4653 def _reconcile_canary( 

4654 self, 

4655 name: str, 

4656 namespace: str, 

4657 spec: dict[str, Any], 

4658 canary: dict[str, Any], 

4659 endpoint: dict[str, Any], 

4660 ) -> dict[str, Any]: 

4661 """Reconcile a classic canary and return observed readiness for routing.""" 

4662 canary_image_value = canary.get("image") 

4663 if not isinstance(canary_image_value, str) or not canary_image_value.strip(): 

4664 raise ValueError("canary.image must be a non-empty string") 

4665 canary_image = canary_image_value.strip() 

4666 

4667 canary_replicas = canary.get("replicas", 1) 

4668 if ( 

4669 not isinstance(canary_replicas, int) 

4670 or isinstance(canary_replicas, bool) 

4671 or canary_replicas < 1 

4672 ): 

4673 raise ValueError("canary.replicas must be a positive integer") 

4674 

4675 canary_weight = canary.get("weight", 10) 

4676 if ( 

4677 not isinstance(canary_weight, int) 

4678 or isinstance(canary_weight, bool) 

4679 or not 1 <= canary_weight <= 99 

4680 ): 

4681 raise ValueError("canary.weight must be an integer between 1 and 99") 

4682 

4683 canary_name = f"{name}-canary" 

4684 del endpoint # Per-endpoint routing metadata is legacy and intentionally ignored. 

4685 

4686 canary_spec = dict(spec) 

4687 canary_spec["image"] = canary_image 

4688 canary_spec["replicas"] = canary_replicas 

4689 canary_spec.pop("canary", None) 

4690 # A canary image is explicit and global. Retaining the primary's 

4691 # region_image_uris would silently deploy the old regional image. 

4692 canary_spec.pop("region_image_uris", None) 

4693 

4694 deployment = self._get_deployment(canary_name, namespace) 

4695 state = "creating" 

4696 ready_replicas = 0 

4697 if deployment is None: 

4698 logger.info("Creating canary deployment %s with image %s", canary_name, canary_image) 

4699 self._create_deployment(canary_name, namespace, canary_spec) 

4700 self._create_service(canary_name, namespace, canary_spec) 

4701 else: 

4702 self._ensure_service(canary_name, namespace, canary_spec) 

4703 current_image = self._get_deployment_image(deployment) 

4704 current_replicas = deployment.spec.replicas or 1 

4705 ready_replicas = deployment.status.ready_replicas or 0 

4706 if current_image != canary_image: 

4707 self._update_deployment_image(canary_name, namespace, canary_image) 

4708 ready_replicas = 0 

4709 state = "updating" 

4710 elif current_replicas != canary_replicas: 

4711 self._scale_deployment(canary_name, namespace, canary_replicas) 

4712 ready_replicas = min(ready_replicas, canary_replicas) 

4713 state = "updating" 

4714 elif ready_replicas >= canary_replicas: 

4715 state = "running" 

4716 

4717 # Canary selection happens behind ``gco-system/inference-proxy``; the 

4718 # shared ``gco-system/gco-gateway`` HTTPRoute is never changed per 

4719 # endpoint. The shared inference proxy consumes the observed canary 

4720 # status returned here. 

4721 return { 

4722 "state": state, 

4723 "image": canary_image, 

4724 "weight": canary_weight, 

4725 "replicas_ready": ready_replicas, 

4726 "replicas_desired": canary_replicas, 

4727 } 

4728 

4729 def _cleanup_canary(self, name: str, namespace: str) -> None: 

4730 """Remove only canary objects carrying this reconciliation authority.""" 

4731 canary_name = f"{name}-canary" 

4732 deployment = self._get_deployment(canary_name, namespace) 

4733 if deployment is not None: 

4734 self._assert_mutation_authority() 

4735 try: 

4736 self.apps_v1.delete_namespaced_deployment( 

4737 canary_name, 

4738 namespace, 

4739 body=self._delete_options_for( 

4740 deployment, kind="deployment", resource_name=canary_name 

4741 ), 

4742 _request_timeout=self._k8s_timeout, 

4743 ) 

4744 logger.info("Deleted canary deployment %s", canary_name) 

4745 except ApiException as error: 

4746 if error.status != 404: 

4747 logger.error("Failed to delete canary deployment %s: %s", canary_name, error) 

4748 try: 

4749 service = self._authorize_existing_service(canary_name, namespace) 

4750 except ApiException as error: 

4751 if error.status != 404: 

4752 logger.error("Failed to read canary service %s: %s", canary_name, error) 

4753 else: 

4754 self._assert_mutation_authority() 

4755 try: 

4756 self.core_v1.delete_namespaced_service( 

4757 canary_name, 

4758 namespace, 

4759 body=self._delete_options_for( 

4760 service, kind="service", resource_name=canary_name 

4761 ), 

4762 _request_timeout=self._k8s_timeout, 

4763 ) 

4764 logger.info("Deleted canary service %s", canary_name) 

4765 except ApiException as error: 

4766 if error.status != 404: 

4767 logger.error("Failed to delete canary service %s: %s", canary_name, error) 

4768 

4769 @staticmethod 

4770 def _endpoint_resource_inventory(name: str) -> EndpointResourceInventory: 

4771 """Return every deterministic endpoint-owned resource name. 

4772 

4773 The shared ``mooncake-master`` Service/StatefulSet and any Secret named 

4774 by the user are deliberately excluded. 

4775 """ 

4776 return EndpointResourceInventory( 

4777 deployments=( 

4778 name, 

4779 f"{name}-canary", 

4780 f"{name}-prefill", 

4781 f"{name}-decode", 

4782 f"{name}-proxy", 

4783 ), 

4784 services=( 

4785 name, 

4786 f"{name}-canary", 

4787 f"{name}-prefill", 

4788 f"{name}-decode", 

4789 f"{name}-proxy", 

4790 ), 

4791 horizontal_pod_autoscalers=( 

4792 name, 

4793 f"{name}-prefill", 

4794 f"{name}-decode", 

4795 f"keda-hpa-{name}", 

4796 f"keda-hpa-{name}-prefill", 

4797 f"keda-hpa-{name}-decode", 

4798 ), 

4799 scaled_objects=( 

4800 name, 

4801 f"{name}-prefill", 

4802 f"{name}-decode", 

4803 ), 

4804 config_maps=(f"{name}-mooncake", f"{name}-pd-proxy"), 

4805 legacy_ingresses=(name, f"{name}-canary", f"{name}-proxy"), 

4806 legacy_http_routes=(name, f"{name}-canary", f"{name}-proxy"), 

4807 generated_admin_secret=f"{name}-admin", 

4808 ) 

4809 

4810 @staticmethod 

4811 def _cleanup_error( 

4812 operation: str, 

4813 kind: str, 

4814 resource_name: str, 

4815 error: Exception, 

4816 ) -> str: 

4817 """Build a bounded error containing only an endpoint-owned name.""" 

4818 status_value = getattr(error, "status", None) 

4819 status = f" (status {status_value})" if status_value is not None else "" 

4820 return f"{operation} {kind} {resource_name} failed{status}" 

4821 

4822 def _delete_and_confirm( 

4823 self, 

4824 *, 

4825 kind: str, 

4826 resource_name: str, 

4827 delete_call: Callable[..., Any], 

4828 read_call: Callable[[], Any], 

4829 patch_metadata: Callable[..., Any] | None, 

4830 pending: list[str], 

4831 errors: list[str], 

4832 observed_resource: Any | None = None, 

4833 ) -> bool: 

4834 """Read-authorize-delete one exact UID/resourceVersion, then re-observe.""" 

4835 resource_id = f"{kind}/{resource_name}" 

4836 if observed_resource is not None: 

4837 observed = observed_resource 

4838 else: 

4839 try: 

4840 observed = read_call() 

4841 except ApiException as error: 

4842 if error.status == 404: 

4843 return False 

4844 errors.append(self._cleanup_error("read", kind, resource_name, error)) 

4845 return False 

4846 except Exception as error: 

4847 errors.append(self._cleanup_error("read", kind, resource_name, error)) 

4848 return False 

4849 

4850 observed = self._authorize_resource( 

4851 observed, 

4852 kind=kind, 

4853 resource_name=resource_name, 

4854 patch_metadata=patch_metadata, 

4855 read_resource=read_call, 

4856 delete_resource=delete_call, 

4857 allow_region_mismatch=bool( 

4858 self._active_authority and self._active_authority.region_removed 

4859 ), 

4860 ) 

4861 _metadata, _annotations, observed_uid, _resource_version = self._object_metadata(observed) 

4862 self._assert_mutation_authority() 

4863 try: 

4864 delete_call( 

4865 body=self._delete_options_for(observed, kind=kind, resource_name=resource_name) 

4866 ) 

4867 except ApiException as error: 

4868 if error.status != 404: 

4869 errors.append(self._cleanup_error("delete", kind, resource_name, error)) 

4870 except Exception as error: 

4871 errors.append(self._cleanup_error("delete", kind, resource_name, error)) 

4872 

4873 try: 

4874 remaining = read_call() 

4875 except ApiException as error: 

4876 if error.status == 404: 

4877 return True 

4878 errors.append(self._cleanup_error("read", kind, resource_name, error)) 

4879 return True 

4880 except Exception as error: 

4881 errors.append(self._cleanup_error("read", kind, resource_name, error)) 

4882 return True 

4883 

4884 _metadata, _annotations, remaining_uid, _remaining_version = self._object_metadata( 

4885 remaining 

4886 ) 

4887 # A replacement with a different UID is never deleted by this stale 

4888 # observation. Its continued presence intentionally blocks cleanup. 

4889 if observed_uid and remaining_uid and remaining_uid != observed_uid: 

4890 pending.append(f"{resource_id}:replacement") 

4891 else: 

4892 pending.append(resource_id) 

4893 return True 

4894 

4895 @staticmethod 

4896 def _merge_cleanup_results(*results: ResourceCleanupResult) -> ResourceCleanupResult: 

4897 """Combine independently attempted cleanup groups without losing failures.""" 

4898 return ResourceCleanupResult( 

4899 pending=tuple(item for result in results for item in result.pending), 

4900 errors=tuple(item for result in results for item in result.errors), 

4901 resources_found=any(result.resources_found for result in results), 

4902 ) 

4903 

4904 def _delete_scaled_objects( 

4905 self, 

4906 names: tuple[str, ...], 

4907 namespace: str, 

4908 ) -> ResourceCleanupResult: 

4909 """Delete and verify the requested KEDA ScaledObjects.""" 

4910 pending: list[str] = [] 

4911 errors: list[str] = [] 

4912 resources_found = False 

4913 custom_objects = client.CustomObjectsApi() 

4914 for autoscaler_name in names: 

4915 resources_found |= self._delete_and_confirm( 

4916 kind="scaledobject", 

4917 resource_name=autoscaler_name, 

4918 delete_call=partial( 

4919 custom_objects.delete_namespaced_custom_object, 

4920 group=KEDA_API_GROUP, 

4921 version=KEDA_API_VERSION, 

4922 namespace=namespace, 

4923 plural=KEDA_SCALEDOBJECT_PLURAL, 

4924 name=autoscaler_name, 

4925 _request_timeout=self._k8s_timeout, 

4926 ), 

4927 read_call=partial( 

4928 custom_objects.get_namespaced_custom_object, 

4929 group=KEDA_API_GROUP, 

4930 version=KEDA_API_VERSION, 

4931 namespace=namespace, 

4932 plural=KEDA_SCALEDOBJECT_PLURAL, 

4933 name=autoscaler_name, 

4934 _request_timeout=self._k8s_timeout, 

4935 ), 

4936 patch_metadata=partial( 

4937 custom_objects.patch_namespaced_custom_object, 

4938 group=KEDA_API_GROUP, 

4939 version=KEDA_API_VERSION, 

4940 namespace=namespace, 

4941 plural=KEDA_SCALEDOBJECT_PLURAL, 

4942 name=autoscaler_name, 

4943 _request_timeout=self._k8s_timeout, 

4944 ), 

4945 pending=pending, 

4946 errors=errors, 

4947 ) 

4948 return ResourceCleanupResult( 

4949 pending=tuple(pending), 

4950 errors=tuple(errors), 

4951 resources_found=resources_found, 

4952 ) 

4953 

4954 def _delete_hpas( 

4955 self, 

4956 names: tuple[str, ...], 

4957 namespace: str, 

4958 ) -> ResourceCleanupResult: 

4959 """Delete and verify the requested native or KEDA-generated HPAs.""" 

4960 pending: list[str] = [] 

4961 errors: list[str] = [] 

4962 resources_found = False 

4963 autoscaling_v2 = client.AutoscalingV2Api() 

4964 for autoscaler_name in names: 

4965 resources_found |= self._delete_and_confirm( 

4966 kind="hpa", 

4967 resource_name=autoscaler_name, 

4968 delete_call=partial( 

4969 autoscaling_v2.delete_namespaced_horizontal_pod_autoscaler, 

4970 autoscaler_name, 

4971 namespace, 

4972 _request_timeout=self._k8s_timeout, 

4973 ), 

4974 read_call=partial( 

4975 autoscaling_v2.read_namespaced_horizontal_pod_autoscaler, 

4976 autoscaler_name, 

4977 namespace, 

4978 _request_timeout=self._k8s_timeout, 

4979 ), 

4980 patch_metadata=partial( 

4981 autoscaling_v2.patch_namespaced_horizontal_pod_autoscaler, 

4982 autoscaler_name, 

4983 namespace, 

4984 _request_timeout=self._k8s_timeout, 

4985 ), 

4986 pending=pending, 

4987 errors=errors, 

4988 ) 

4989 return ResourceCleanupResult( 

4990 pending=tuple(pending), 

4991 errors=tuple(errors), 

4992 resources_found=resources_found, 

4993 ) 

4994 

4995 def _delete_autoscalers( 

4996 self, 

4997 scaled_object_names: tuple[str, ...], 

4998 hpa_names: tuple[str, ...], 

4999 namespace: str, 

5000 ) -> ResourceCleanupResult: 

5001 """Delete every autoscaler owner and wait for all of them to disappear.""" 

5002 # KEDA must stop first; only then is it safe to remove its generated HPA 

5003 # alongside any native HPA that may remain from a prior configuration. 

5004 scaled_objects = self._delete_scaled_objects(scaled_object_names, namespace) 

5005 hpas = self._delete_hpas(hpa_names, namespace) 

5006 return self._merge_cleanup_results(scaled_objects, hpas) 

5007 

5008 @staticmethod 

5009 def _generated_admin_secret_labels(expected_name: str) -> dict[str, str]: 

5010 """Return the schema-valid static provenance used by generated Secrets.""" 

5011 return { 

5012 "app": expected_name, 

5013 "project": "gco", 

5014 "gco.io/type": "inference", 

5015 } 

5016 

5017 @classmethod 

5018 def _is_monitor_owned_admin_secret( 

5019 cls, 

5020 secret: Any, 

5021 expected_name: str, 

5022 expected_lifecycle_id: str, 

5023 ) -> bool: 

5024 """Return whether a generated Secret is bound to this endpoint lifecycle.""" 

5025 metadata = getattr(secret, "metadata", None) 

5026 labels = getattr(metadata, "labels", None) 

5027 annotations = getattr(metadata, "annotations", None) 

5028 return ( 

5029 labels == cls._generated_admin_secret_labels(expected_name) 

5030 and isinstance(annotations, dict) 

5031 and annotations.get("gco.io/lifecycle-id") == expected_lifecycle_id 

5032 ) 

5033 

5034 @classmethod 

5035 def _is_legacy_monitor_admin_secret(cls, secret: Any, expected_name: str) -> bool: 

5036 """Recognize only the exact pre-v7 monitor-generated Secret shape.""" 

5037 metadata = getattr(secret, "metadata", None) 

5038 labels = getattr(metadata, "labels", None) 

5039 annotations = getattr(metadata, "annotations", None) 

5040 lifecycle_annotation = ( 

5041 annotations.get("gco.io/lifecycle-id") if isinstance(annotations, dict) else None 

5042 ) 

5043 secret_type = getattr(secret, "type", None) 

5044 return ( 

5045 labels == cls._generated_admin_secret_labels(expected_name) 

5046 and lifecycle_annotation is None 

5047 and secret_type == "Opaque" 

5048 and cls._secret_has_admin_api_key(secret) 

5049 ) 

5050 

5051 def _adopt_legacy_admin_secret( 

5052 self, 

5053 secret: Any, 

5054 secret_name: str, 

5055 namespace: str, 

5056 lifecycle_id: str, 

5057 ) -> Any: 

5058 """Patch exact legacy provenance and verify the persisted lifecycle annotation.""" 

5059 if not self._is_legacy_monitor_admin_secret(secret, secret_name): 

5060 raise AdminApiKeySecretError( 

5061 secret_name, 

5062 "the conventional generated Secret has ambiguous ownership", 

5063 ) 

5064 metadata = getattr(secret, "metadata", None) 

5065 resource_version = getattr(metadata, "resource_version", None) 

5066 if not isinstance(resource_version, str) or not resource_version: 

5067 raise AdminApiKeySecretError( 

5068 secret_name, 

5069 "legacy generated Secret has no resource version for safe migration", 

5070 ) 

5071 annotations = self._provenance_annotations() or {_LIFECYCLE_ANNOTATION: lifecycle_id} 

5072 self._assert_mutation_authority() 

5073 try: 

5074 self.core_v1.patch_namespaced_secret( 

5075 secret_name, 

5076 namespace, 

5077 body={ 

5078 "metadata": { 

5079 "resourceVersion": resource_version, 

5080 "annotations": annotations, 

5081 } 

5082 }, 

5083 _request_timeout=self._k8s_timeout, 

5084 ) 

5085 except Exception as error: 

5086 raise AdminApiKeySecretError( 

5087 secret_name, 

5088 "legacy generated Secret changed during lifecycle migration", 

5089 ) from error 

5090 adopted = self.core_v1.read_namespaced_secret( 

5091 secret_name, 

5092 namespace, 

5093 _request_timeout=self._k8s_timeout, 

5094 ) 

5095 if not self._is_monitor_owned_admin_secret( 

5096 adopted, 

5097 secret_name, 

5098 lifecycle_id, 

5099 ) or not self._secret_has_admin_api_key(adopted): 

5100 raise AdminApiKeySecretError( 

5101 secret_name, 

5102 "legacy generated Secret migration could not be verified", 

5103 ) 

5104 return adopted 

5105 

5106 def _require_owned_admin_secret( 

5107 self, 

5108 secret: Any, 

5109 secret_name: str, 

5110 namespace: str, 

5111 lifecycle_id: str, 

5112 ) -> Any: 

5113 """Accept current provenance or safely migrate one exact legacy shape.""" 

5114 if self._is_monitor_owned_admin_secret( 

5115 secret, 

5116 secret_name, 

5117 lifecycle_id, 

5118 ) and self._secret_has_admin_api_key(secret): 

5119 return secret 

5120 if self._is_legacy_monitor_admin_secret(secret, secret_name): 

5121 return self._adopt_legacy_admin_secret( 

5122 secret, 

5123 secret_name, 

5124 namespace, 

5125 lifecycle_id, 

5126 ) 

5127 raise AdminApiKeySecretError( 

5128 secret_name, 

5129 "the conventional generated Secret exists without matching lifecycle provenance", 

5130 ) 

5131 

5132 @staticmethod 

5133 def _generated_child_matches( 

5134 item: Any, 

5135 kind: str, 

5136 deployment_names: tuple[str, ...], 

5137 service_names: tuple[str, ...], 

5138 replica_set_names: tuple[str, ...] = (), 

5139 ) -> bool: 

5140 """Match generated children only through exact parent identity.""" 

5141 metadata = getattr(item, "metadata", None) 

5142 child_name = getattr(metadata, "name", None) 

5143 labels = getattr(metadata, "labels", None) 

5144 labels = labels if isinstance(labels, dict) else {} 

5145 owner_references = getattr(metadata, "owner_references", None) 

5146 owners = owner_references if isinstance(owner_references, (list, tuple)) else () 

5147 

5148 if kind in {"replicaset", "pod"}: 

5149 app_name = labels.get("app") 

5150 if app_name not in deployment_names: 

5151 return False 

5152 if labels.get("project") != "gco" or labels.get("gco.io/type") != "inference": 

5153 return False 

5154 if not owners: 

5155 return True 

5156 for owner in owners: 

5157 owner_kind = getattr(owner, "kind", None) 

5158 owner_name = getattr(owner, "name", None) 

5159 if owner_kind == "Deployment" and owner_name in deployment_names: 

5160 return True 

5161 if kind == "pod" and owner_kind == "ReplicaSet" and owner_name in replica_set_names: 

5162 return True 

5163 return False 

5164 

5165 if kind == "endpoints": 

5166 return isinstance(child_name, str) and child_name in service_names 

5167 if kind == "endpointslice": 

5168 service_name = labels.get("kubernetes.io/service-name") 

5169 return isinstance(service_name, str) and service_name in service_names 

5170 return False 

5171 

5172 def _observe_generated_children( 

5173 self, 

5174 name: str, 

5175 namespace: str, 

5176 inventory: EndpointResourceInventory, 

5177 pending: list[str], 

5178 errors: list[str], 

5179 ) -> bool: 

5180 """Inventory exact endpoint children once per kind and dependency order.""" 

5181 resources_found = False 

5182 matched_replica_sets: list[str] = [] 

5183 list_calls: tuple[tuple[str, Callable[[], Any]], ...] = ( 

5184 ( 

5185 "replicaset", 

5186 partial( 

5187 self.apps_v1.list_namespaced_replica_set, 

5188 namespace, 

5189 _request_timeout=self._k8s_timeout, 

5190 ), 

5191 ), 

5192 ( 

5193 "pod", 

5194 partial( 

5195 self.core_v1.list_namespaced_pod, 

5196 namespace, 

5197 _request_timeout=self._k8s_timeout, 

5198 ), 

5199 ), 

5200 ( 

5201 "endpoints", 

5202 partial( 

5203 self.core_v1.list_namespaced_endpoints, 

5204 namespace, 

5205 _request_timeout=self._k8s_timeout, 

5206 ), 

5207 ), 

5208 ( 

5209 "endpointslice", 

5210 partial( 

5211 self.discovery_v1.list_namespaced_endpoint_slice, 

5212 namespace, 

5213 _request_timeout=self._k8s_timeout, 

5214 ), 

5215 ), 

5216 ) 

5217 for kind, list_call in list_calls: 

5218 try: 

5219 response = list_call() 

5220 except ApiException as error: 

5221 errors.append(self._cleanup_error("list", kind, name, error)) 

5222 continue 

5223 except Exception as error: 

5224 errors.append(self._cleanup_error("list", kind, name, error)) 

5225 continue 

5226 items = getattr(response, "items", None) 

5227 if not isinstance(items, (list, tuple)): 

5228 items = () 

5229 for item in items: 

5230 if not self._generated_child_matches( 

5231 item, 

5232 kind, 

5233 inventory.deployments, 

5234 inventory.services, 

5235 tuple(matched_replica_sets), 

5236 ): 

5237 continue 

5238 metadata = getattr(item, "metadata", None) 

5239 child_name = getattr(metadata, "name", "unknown") 

5240 if kind == "replicaset" and isinstance(child_name, str): 

5241 matched_replica_sets.append(child_name) 

5242 pending.append(f"{kind}/{child_name}") 

5243 resources_found = True 

5244 return resources_found 

5245 

5246 def _delete_resources( 

5247 self, 

5248 name: str, 

5249 namespace: str, 

5250 spec: dict[str, Any] | None = None, 

5251 *, 

5252 expected_lifecycle_id: str | None = None, 

5253 ) -> ResourceCleanupResult: 

5254 """Delete parents and prove all top-level/generated children absent.""" 

5255 inventory = self._endpoint_resource_inventory(name) 

5256 autoscaler_cleanup = self._delete_autoscalers( 

5257 inventory.scaled_objects, 

5258 inventory.horizontal_pod_autoscalers, 

5259 namespace, 

5260 ) 

5261 pending = list(autoscaler_cleanup.pending) 

5262 errors = list(autoscaler_cleanup.errors) 

5263 resources_found = autoscaler_cleanup.resources_found 

5264 

5265 for deployment_name in inventory.deployments: 

5266 resources_found |= self._delete_and_confirm( 

5267 kind="deployment", 

5268 resource_name=deployment_name, 

5269 delete_call=partial( 

5270 self.apps_v1.delete_namespaced_deployment, 

5271 deployment_name, 

5272 namespace, 

5273 _request_timeout=self._k8s_timeout, 

5274 ), 

5275 read_call=partial( 

5276 self.apps_v1.read_namespaced_deployment, 

5277 deployment_name, 

5278 namespace, 

5279 _request_timeout=self._k8s_timeout, 

5280 ), 

5281 patch_metadata=partial( 

5282 self.apps_v1.patch_namespaced_deployment, 

5283 deployment_name, 

5284 namespace, 

5285 _request_timeout=self._k8s_timeout, 

5286 ), 

5287 pending=pending, 

5288 errors=errors, 

5289 ) 

5290 

5291 for service_name in inventory.services: 

5292 resources_found |= self._delete_and_confirm( 

5293 kind="service", 

5294 resource_name=service_name, 

5295 delete_call=partial( 

5296 self.core_v1.delete_namespaced_service, 

5297 service_name, 

5298 namespace, 

5299 _request_timeout=self._k8s_timeout, 

5300 ), 

5301 read_call=partial( 

5302 self.core_v1.read_namespaced_service, 

5303 service_name, 

5304 namespace, 

5305 _request_timeout=self._k8s_timeout, 

5306 ), 

5307 patch_metadata=partial( 

5308 self.core_v1.patch_namespaced_service, 

5309 service_name, 

5310 namespace, 

5311 _request_timeout=self._k8s_timeout, 

5312 ), 

5313 pending=pending, 

5314 errors=errors, 

5315 ) 

5316 

5317 for config_map_name in inventory.config_maps: 

5318 resources_found |= self._delete_and_confirm( 

5319 kind="configmap", 

5320 resource_name=config_map_name, 

5321 delete_call=partial( 

5322 self.core_v1.delete_namespaced_config_map, 

5323 config_map_name, 

5324 namespace, 

5325 _request_timeout=self._k8s_timeout, 

5326 ), 

5327 read_call=partial( 

5328 self.core_v1.read_namespaced_config_map, 

5329 config_map_name, 

5330 namespace, 

5331 _request_timeout=self._k8s_timeout, 

5332 ), 

5333 patch_metadata=partial( 

5334 self.core_v1.patch_namespaced_config_map, 

5335 config_map_name, 

5336 namespace, 

5337 _request_timeout=self._k8s_timeout, 

5338 ), 

5339 pending=pending, 

5340 errors=errors, 

5341 ) 

5342 

5343 for ingress_name in inventory.legacy_ingresses: 

5344 resources_found |= self._delete_and_confirm( 

5345 kind="ingress", 

5346 resource_name=ingress_name, 

5347 delete_call=partial( 

5348 self.networking_v1.delete_namespaced_ingress, 

5349 ingress_name, 

5350 namespace, 

5351 _request_timeout=self._k8s_timeout, 

5352 ), 

5353 read_call=partial( 

5354 self.networking_v1.read_namespaced_ingress, 

5355 ingress_name, 

5356 namespace, 

5357 _request_timeout=self._k8s_timeout, 

5358 ), 

5359 patch_metadata=partial( 

5360 self.networking_v1.patch_namespaced_ingress, 

5361 ingress_name, 

5362 namespace, 

5363 _request_timeout=self._k8s_timeout, 

5364 ), 

5365 pending=pending, 

5366 errors=errors, 

5367 ) 

5368 

5369 custom_objects = client.CustomObjectsApi() 

5370 for route_name in inventory.legacy_http_routes: 

5371 resources_found |= self._delete_and_confirm( 

5372 kind="httproute", 

5373 resource_name=route_name, 

5374 delete_call=partial( 

5375 custom_objects.delete_namespaced_custom_object, 

5376 group="gateway.networking.k8s.io", 

5377 version="v1", 

5378 namespace=namespace, 

5379 plural="httproutes", 

5380 name=route_name, 

5381 _request_timeout=self._k8s_timeout, 

5382 ), 

5383 read_call=partial( 

5384 custom_objects.get_namespaced_custom_object, 

5385 group="gateway.networking.k8s.io", 

5386 version="v1", 

5387 namespace=namespace, 

5388 plural="httproutes", 

5389 name=route_name, 

5390 _request_timeout=self._k8s_timeout, 

5391 ), 

5392 patch_metadata=partial( 

5393 custom_objects.patch_namespaced_custom_object, 

5394 group="gateway.networking.k8s.io", 

5395 version="v1", 

5396 namespace=namespace, 

5397 plural="httproutes", 

5398 name=route_name, 

5399 _request_timeout=self._k8s_timeout, 

5400 ), 

5401 pending=pending, 

5402 errors=errors, 

5403 ) 

5404 

5405 resources_found |= self._observe_generated_children( 

5406 name, 

5407 namespace, 

5408 inventory, 

5409 pending, 

5410 errors, 

5411 ) 

5412 

5413 # A user-named Secret is external and survives. The conventional name 

5414 # is auto-managed only when both labels and immutable provenance match; 

5415 # an ambiguous same-name Secret blocks terminal cleanup. 

5416 mooncake = spec.get("mooncake") if isinstance(spec, dict) else None 

5417 proxy = mooncake.get("proxy") if isinstance(mooncake, dict) else None 

5418 named_secret = proxy.get("admin_api_key_secret") if isinstance(proxy, dict) else None 

5419 generated_secret = inventory.generated_admin_secret 

5420 if named_secret != generated_secret: 

5421 try: 

5422 secret = self.core_v1.read_namespaced_secret( 

5423 generated_secret, 

5424 namespace, 

5425 _request_timeout=self._k8s_timeout, 

5426 ) 

5427 except ApiException as error: 

5428 if error.status != 404: 

5429 errors.append(self._cleanup_error("read", "secret", generated_secret, error)) 

5430 except Exception as error: 

5431 errors.append(self._cleanup_error("read", "secret", generated_secret, error)) 

5432 else: 

5433 resources_found = True 

5434 if isinstance(expected_lifecycle_id, str) and expected_lifecycle_id: 

5435 try: 

5436 owned_secret = self._require_owned_admin_secret( 

5437 secret, 

5438 generated_secret, 

5439 namespace, 

5440 expected_lifecycle_id, 

5441 ) 

5442 except AdminApiKeySecretError: 

5443 errors.append( 

5444 f"ambiguous secret {generated_secret} is not owned by lifecycle " 

5445 f"{expected_lifecycle_id}" 

5446 ) 

5447 else: 

5448 resources_found |= self._delete_and_confirm( 

5449 kind="secret", 

5450 resource_name=generated_secret, 

5451 delete_call=partial( 

5452 self.core_v1.delete_namespaced_secret, 

5453 generated_secret, 

5454 namespace, 

5455 _request_timeout=self._k8s_timeout, 

5456 ), 

5457 read_call=partial( 

5458 self.core_v1.read_namespaced_secret, 

5459 generated_secret, 

5460 namespace, 

5461 _request_timeout=self._k8s_timeout, 

5462 ), 

5463 patch_metadata=partial( 

5464 self.core_v1.patch_namespaced_secret, 

5465 generated_secret, 

5466 namespace, 

5467 _request_timeout=self._k8s_timeout, 

5468 ), 

5469 pending=pending, 

5470 errors=errors, 

5471 observed_resource=owned_secret, 

5472 ) 

5473 else: 

5474 errors.append( 

5475 f"ambiguous secret {generated_secret} is not owned by lifecycle unknown" 

5476 ) 

5477 

5478 return ResourceCleanupResult( 

5479 pending=tuple(sorted(set(pending))), 

5480 errors=tuple(errors), 

5481 resources_found=resources_found, 

5482 ) 

5483 

5484 def _build_hpa_metrics(self, metrics_config: list[dict[str, Any]]) -> list[Any]: 

5485 """Translate a metrics config list into autoscaler metric specs. 

5486 

5487 Each entry names a resource (``cpu`` or ``memory``) and a target 

5488 average utilization. Unrecognized entries are skipped, and when nothing 

5489 recognizable remains the autoscaler falls back to scaling on CPU at 70% 

5490 so a Deployment is never left without a scaling signal. 

5491 """ 

5492 hpa_metrics = [] 

5493 for m in metrics_config: 

5494 metric_type = m.get("type", "cpu") 

5495 target_value = m.get("target", 70) 

5496 

5497 if metric_type == "cpu": 

5498 hpa_metrics.append( 

5499 client.V2MetricSpec( 

5500 type="Resource", 

5501 resource=client.V2ResourceMetricSource( 

5502 name="cpu", 

5503 target=client.V2MetricTarget( 

5504 type="Utilization", 

5505 average_utilization=target_value, 

5506 ), 

5507 ), 

5508 ) 

5509 ) 

5510 elif metric_type == "memory": 

5511 hpa_metrics.append( 

5512 client.V2MetricSpec( 

5513 type="Resource", 

5514 resource=client.V2ResourceMetricSource( 

5515 name="memory", 

5516 target=client.V2MetricTarget( 

5517 type="Utilization", 

5518 average_utilization=target_value, 

5519 ), 

5520 ), 

5521 ) 

5522 ) 

5523 

5524 if not hpa_metrics: 

5525 # Default to CPU if no recognized metrics 

5526 hpa_metrics.append( 

5527 client.V2MetricSpec( 

5528 type="Resource", 

5529 resource=client.V2ResourceMetricSource( 

5530 name="cpu", 

5531 target=client.V2MetricTarget( 

5532 type="Utilization", 

5533 average_utilization=70, 

5534 ), 

5535 ), 

5536 ) 

5537 ) 

5538 

5539 return hpa_metrics 

5540 

5541 @staticmethod 

5542 def _metrics_require_keda(metrics_config: list[dict[str, Any]]) -> bool: 

5543 """Return True when any metric can only be scaled via KEDA/CloudWatch. 

5544 

5545 GPU metrics are not Kubernetes Resource metrics, so a native HPA cannot 

5546 consume them. Their presence forces the whole autoscaler onto the KEDA 

5547 ScaledObject path, where cpu/memory targets become native KEDA triggers 

5548 alongside the aws-cloudwatch GPU trigger. 

5549 """ 

5550 return any(m.get("type") in _CLOUDWATCH_METRIC_BY_TYPE for m in metrics_config) 

5551 

5552 def _build_keda_triggers( 

5553 self, 

5554 metrics_config: list[dict[str, Any]], 

5555 target_name: str, 

5556 namespace: str, 

5557 ) -> list[dict[str, Any]]: 

5558 """Translate a metrics config list into KEDA ScaledObject triggers. 

5559 

5560 ``cpu`` and ``memory`` map to KEDA's native resource triggers (the same 

5561 utilization signal a plain HPA would use). ``gpu``/``gpu_memory`` map to 

5562 an ``aws-cloudwatch`` trigger reading the matching ContainerInsights 

5563 metric for this Deployment, identified by the 

5564 ClusterName/Namespace/PodName dimension triple. Unrecognized entries are 

5565 skipped; when nothing recognizable remains the autoscaler falls back to 

5566 CPU at 70% so a Deployment is never left without a scaling signal. 

5567 """ 

5568 triggers: list[dict[str, Any]] = [] 

5569 for m in metrics_config: 

5570 metric_type = m.get("type", "cpu") 

5571 target_value = m.get("target", 70) 

5572 

5573 if metric_type in ("cpu", "memory"): 

5574 triggers.append( 

5575 { 

5576 "type": metric_type, 

5577 "metricType": "Utilization", 

5578 "metadata": {"value": str(target_value)}, 

5579 } 

5580 ) 

5581 elif metric_type in _CLOUDWATCH_METRIC_BY_TYPE: 

5582 triggers.append( 

5583 { 

5584 "type": "aws-cloudwatch", 

5585 "metadata": { 

5586 "namespace": GPU_METRIC_NAMESPACE, 

5587 "metricName": _CLOUDWATCH_METRIC_BY_TYPE[metric_type], 

5588 "dimensionName": "ClusterName;Namespace;PodName", 

5589 "dimensionValue": f"{self.cluster_id};{namespace};{target_name}", 

5590 "targetMetricValue": str(target_value), 

5591 "minMetricValue": "0", 

5592 "metricStat": "Average", 

5593 "awsRegion": self.region, 

5594 "identityOwner": "operator", 

5595 }, 

5596 } 

5597 ) 

5598 

5599 if not triggers: 

5600 triggers.append( 

5601 { 

5602 "type": "cpu", 

5603 "metricType": "Utilization", 

5604 "metadata": {"value": "70"}, 

5605 } 

5606 ) 

5607 

5608 return triggers 

5609 

5610 def _apply_scaled_object( 

5611 self, 

5612 name: str, 

5613 namespace: str, 

5614 target_name: str, 

5615 min_replicas: int, 

5616 max_replicas: int, 

5617 metrics_config: list[dict[str, Any]], 

5618 ) -> None: 

5619 """Create or patch a KEDA ScaledObject targeting one Deployment. 

5620 

5621 Used whenever the metric set includes a GPU signal (see 

5622 :meth:`_metrics_require_keda`). KEDA owns the backing HPA and reads GPU 

5623 utilization from CloudWatch via the keda-operator's IRSA role, scaling 

5624 ``target_name`` between ``min_replicas`` and ``max_replicas``. An 

5625 already-present ScaledObject of the same name is merge-patched rather 

5626 than duplicated. 

5627 """ 

5628 body: dict[str, Any] = { 

5629 "apiVersion": f"{KEDA_API_GROUP}/{KEDA_API_VERSION}", 

5630 "kind": "ScaledObject", 

5631 "metadata": { 

5632 "name": name, 

5633 "namespace": namespace, 

5634 "labels": { 

5635 "app": name, 

5636 "project": "gco", 

5637 "gco.io/type": "inference", 

5638 }, 

5639 "annotations": self._provenance_annotations() or {}, 

5640 }, 

5641 "spec": { 

5642 "scaleTargetRef": {"name": target_name}, 

5643 "minReplicaCount": min_replicas, 

5644 "maxReplicaCount": max_replicas, 

5645 "triggers": self._build_keda_triggers(metrics_config, target_name, namespace), 

5646 }, 

5647 } 

5648 

5649 custom = client.CustomObjectsApi() 

5650 self._assert_mutation_authority() 

5651 try: 

5652 custom.create_namespaced_custom_object( 

5653 group=KEDA_API_GROUP, 

5654 version=KEDA_API_VERSION, 

5655 namespace=namespace, 

5656 plural=KEDA_SCALEDOBJECT_PLURAL, 

5657 body=body, 

5658 _request_timeout=self._k8s_timeout, 

5659 ) 

5660 self._confirm_created_resource( 

5661 kind="scaledobject", 

5662 resource_name=name, 

5663 read_resource=partial( 

5664 custom.get_namespaced_custom_object, 

5665 group=KEDA_API_GROUP, 

5666 version=KEDA_API_VERSION, 

5667 namespace=namespace, 

5668 plural=KEDA_SCALEDOBJECT_PLURAL, 

5669 name=name, 

5670 _request_timeout=self._k8s_timeout, 

5671 ), 

5672 delete_resource=partial( 

5673 custom.delete_namespaced_custom_object, 

5674 group=KEDA_API_GROUP, 

5675 version=KEDA_API_VERSION, 

5676 namespace=namespace, 

5677 plural=KEDA_SCALEDOBJECT_PLURAL, 

5678 name=name, 

5679 _request_timeout=self._k8s_timeout, 

5680 ), 

5681 ) 

5682 logger.info( 

5683 "Created KEDA ScaledObject %s targeting %s (min=%d, max=%d)", 

5684 name, 

5685 target_name, 

5686 min_replicas, 

5687 max_replicas, 

5688 ) 

5689 except ApiException as error: 

5690 if error.status != 409: 

5691 raise 

5692 existing = custom.get_namespaced_custom_object( 

5693 group=KEDA_API_GROUP, 

5694 version=KEDA_API_VERSION, 

5695 namespace=namespace, 

5696 plural=KEDA_SCALEDOBJECT_PLURAL, 

5697 name=name, 

5698 _request_timeout=self._k8s_timeout, 

5699 ) 

5700 existing = self._authorize_resource( 

5701 existing, 

5702 kind="scaledobject", 

5703 resource_name=name, 

5704 patch_metadata=partial( 

5705 custom.patch_namespaced_custom_object, 

5706 group=KEDA_API_GROUP, 

5707 version=KEDA_API_VERSION, 

5708 namespace=namespace, 

5709 plural=KEDA_SCALEDOBJECT_PLURAL, 

5710 name=name, 

5711 _request_timeout=self._k8s_timeout, 

5712 ), 

5713 read_resource=lambda: custom.get_namespaced_custom_object( 

5714 group=KEDA_API_GROUP, 

5715 version=KEDA_API_VERSION, 

5716 namespace=namespace, 

5717 plural=KEDA_SCALEDOBJECT_PLURAL, 

5718 name=name, 

5719 _request_timeout=self._k8s_timeout, 

5720 ), 

5721 delete_resource=partial( 

5722 custom.delete_namespaced_custom_object, 

5723 group=KEDA_API_GROUP, 

5724 version=KEDA_API_VERSION, 

5725 namespace=namespace, 

5726 plural=KEDA_SCALEDOBJECT_PLURAL, 

5727 name=name, 

5728 _request_timeout=self._k8s_timeout, 

5729 ), 

5730 ) 

5731 _metadata, _annotations, _uid, resource_version = self._object_metadata(existing) 

5732 body["metadata"]["resourceVersion"] = resource_version 

5733 self._assert_mutation_authority() 

5734 custom.patch_namespaced_custom_object( 

5735 group=KEDA_API_GROUP, 

5736 version=KEDA_API_VERSION, 

5737 namespace=namespace, 

5738 plural=KEDA_SCALEDOBJECT_PLURAL, 

5739 name=name, 

5740 body=body, 

5741 _request_timeout=self._k8s_timeout, 

5742 ) 

5743 logger.info("Updated KEDA ScaledObject %s", name) 

5744 

5745 def _apply_hpa( 

5746 self, 

5747 hpa_name: str, 

5748 namespace: str, 

5749 target_name: str, 

5750 min_replicas: int, 

5751 max_replicas: int, 

5752 metrics_config: list[dict[str, Any]], 

5753 ) -> None: 

5754 """Create or patch a single autoscaler targeting one Deployment. 

5755 

5756 Builds a V2 autoscaler that scales ``target_name`` between 

5757 ``min_replicas`` and ``max_replicas`` on the given metrics, then creates 

5758 it. An already-present autoscaler of the same name is patched in place 

5759 rather than duplicated. When the metric set includes a GPU signal the 

5760 autoscaler is materialized as a KEDA ScaledObject instead (native HPA 

5761 Resource metrics cannot read GPU utilization). 

5762 """ 

5763 if self._metrics_require_keda(metrics_config): 

5764 self._apply_scaled_object( 

5765 name=hpa_name, 

5766 namespace=namespace, 

5767 target_name=target_name, 

5768 min_replicas=min_replicas, 

5769 max_replicas=max_replicas, 

5770 metrics_config=metrics_config, 

5771 ) 

5772 return 

5773 

5774 hpa = client.V2HorizontalPodAutoscaler( 

5775 metadata=client.V1ObjectMeta( 

5776 name=hpa_name, 

5777 namespace=namespace, 

5778 labels={ 

5779 "app": hpa_name, 

5780 "project": "gco", 

5781 "gco.io/type": "inference", 

5782 }, 

5783 annotations=self._provenance_annotations(), 

5784 ), 

5785 spec=client.V2HorizontalPodAutoscalerSpec( 

5786 scale_target_ref=client.V2CrossVersionObjectReference( 

5787 api_version="apps/v1", 

5788 kind="Deployment", 

5789 name=target_name, 

5790 ), 

5791 min_replicas=min_replicas, 

5792 max_replicas=max_replicas, 

5793 metrics=self._build_hpa_metrics(metrics_config), 

5794 ), 

5795 ) 

5796 

5797 autoscaling_v2 = client.AutoscalingV2Api() 

5798 self._assert_mutation_authority() 

5799 try: 

5800 autoscaling_v2.create_namespaced_horizontal_pod_autoscaler(namespace, hpa) 

5801 self._confirm_created_resource( 

5802 kind="hpa", 

5803 resource_name=hpa_name, 

5804 read_resource=partial( 

5805 autoscaling_v2.read_namespaced_horizontal_pod_autoscaler, 

5806 hpa_name, 

5807 namespace, 

5808 _request_timeout=self._k8s_timeout, 

5809 ), 

5810 delete_resource=partial( 

5811 autoscaling_v2.delete_namespaced_horizontal_pod_autoscaler, 

5812 hpa_name, 

5813 namespace, 

5814 _request_timeout=self._k8s_timeout, 

5815 ), 

5816 ) 

5817 logger.info( 

5818 "Created HPA %s targeting %s (min=%d, max=%d)", 

5819 hpa_name, 

5820 target_name, 

5821 min_replicas, 

5822 max_replicas, 

5823 ) 

5824 except ApiException as error: 

5825 if error.status != 409: 

5826 raise 

5827 existing = autoscaling_v2.read_namespaced_horizontal_pod_autoscaler( 

5828 hpa_name, namespace, _request_timeout=self._k8s_timeout 

5829 ) 

5830 existing = self._authorize_resource( 

5831 existing, 

5832 kind="hpa", 

5833 resource_name=hpa_name, 

5834 patch_metadata=partial( 

5835 autoscaling_v2.patch_namespaced_horizontal_pod_autoscaler, 

5836 hpa_name, 

5837 namespace, 

5838 _request_timeout=self._k8s_timeout, 

5839 ), 

5840 read_resource=lambda: autoscaling_v2.read_namespaced_horizontal_pod_autoscaler( 

5841 hpa_name, namespace, _request_timeout=self._k8s_timeout 

5842 ), 

5843 delete_resource=partial( 

5844 autoscaling_v2.delete_namespaced_horizontal_pod_autoscaler, 

5845 hpa_name, 

5846 namespace, 

5847 _request_timeout=self._k8s_timeout, 

5848 ), 

5849 ) 

5850 _metadata, _annotations, _uid, resource_version = self._object_metadata(existing) 

5851 hpa.metadata.resource_version = resource_version 

5852 self._assert_mutation_authority() 

5853 autoscaling_v2.patch_namespaced_horizontal_pod_autoscaler( 

5854 hpa_name, 

5855 namespace, 

5856 hpa, 

5857 _request_timeout=self._k8s_timeout, 

5858 ) 

5859 logger.info("Updated HPA %s", hpa_name) 

5860 

5861 def _verify_hpa_owner( 

5862 self, 

5863 hpa_name: str, 

5864 namespace: str, 

5865 target_name: str, 

5866 ) -> ResourceCleanupResult: 

5867 """Verify that the exact native/KEDA HPA owns the expected Deployment.""" 

5868 autoscaling_v2 = client.AutoscalingV2Api() 

5869 try: 

5870 hpa = autoscaling_v2.read_namespaced_horizontal_pod_autoscaler( 

5871 hpa_name, 

5872 namespace, 

5873 ) 

5874 except ApiException as error: 

5875 if error.status == 404: 

5876 return ResourceCleanupResult(pending=(f"hpa/{hpa_name}",)) 

5877 return ResourceCleanupResult( 

5878 errors=(self._cleanup_error("read", "hpa", hpa_name, error),) 

5879 ) 

5880 except Exception as error: 

5881 return ResourceCleanupResult( 

5882 errors=(self._cleanup_error("read", "hpa", hpa_name, error),) 

5883 ) 

5884 try: 

5885 hpa = self._authorize_resource( 

5886 hpa, 

5887 kind="hpa", 

5888 resource_name=hpa_name, 

5889 patch_metadata=partial( 

5890 autoscaling_v2.patch_namespaced_horizontal_pod_autoscaler, 

5891 hpa_name, 

5892 namespace, 

5893 _request_timeout=self._k8s_timeout, 

5894 ), 

5895 read_resource=lambda: autoscaling_v2.read_namespaced_horizontal_pod_autoscaler( 

5896 hpa_name, namespace, _request_timeout=self._k8s_timeout 

5897 ), 

5898 delete_resource=partial( 

5899 autoscaling_v2.delete_namespaced_horizontal_pod_autoscaler, 

5900 hpa_name, 

5901 namespace, 

5902 _request_timeout=self._k8s_timeout, 

5903 ), 

5904 ) 

5905 except ReconcileFencedError: 

5906 raise 

5907 hpa_spec = getattr(hpa, "spec", None) 

5908 target_ref = getattr(hpa_spec, "scale_target_ref", None) 

5909 observed_api_version = getattr(target_ref, "api_version", None) 

5910 observed_kind = getattr(target_ref, "kind", None) 

5911 observed_target = getattr(target_ref, "name", None) 

5912 if ( 

5913 observed_api_version != "apps/v1" 

5914 or observed_kind != "Deployment" 

5915 or observed_target != target_name 

5916 ): 

5917 return ResourceCleanupResult( 

5918 pending=(f"hpa/{hpa_name}",), 

5919 resources_found=True, 

5920 ) 

5921 return ResourceCleanupResult(resources_found=True) 

5922 

5923 def _reconcile_classic_autoscaler( 

5924 self, 

5925 name: str, 

5926 namespace: str, 

5927 spec: dict[str, Any], 

5928 *, 

5929 apply_desired: bool = True, 

5930 ) -> ResourceCleanupResult: 

5931 """Converge a classic endpoint to exactly one autoscaler owner. 

5932 

5933 A stopped target is first restored to a positive minimum, then the 

5934 desired native/KEDA owner is applied and read back before reconciliation 

5935 may proceed. Obsolete owners are always confirmed absent first. 

5936 """ 

5937 autoscaling = spec.get("autoscaling", {}) 

5938 metrics_config = autoscaling.get("metrics", [{"type": "cpu", "target": 70}]) 

5939 keda = self._metrics_require_keda(metrics_config) 

5940 if keda: 

5941 cleanup = self._delete_hpas((name,), namespace) 

5942 else: 

5943 cleanup = self._merge_cleanup_results( 

5944 self._delete_scaled_objects((name,), namespace), 

5945 self._delete_hpas((f"keda-hpa-{name}",), namespace), 

5946 ) 

5947 

5948 if cleanup.complete and apply_desired: 

5949 target = self._get_deployment(name, namespace) 

5950 current = getattr(getattr(target, "spec", None), "replicas", None) 

5951 if target is not None and current == 0: 

5952 minimum = max(1, int(autoscaling.get("min_replicas", 1))) 

5953 self._scale_deployment(name, namespace, minimum) 

5954 self._create_or_update_hpa(name, namespace, spec) 

5955 desired_hpa = f"keda-hpa-{name}" if keda else name 

5956 return self._merge_cleanup_results( 

5957 cleanup, 

5958 self._verify_hpa_owner(desired_hpa, namespace, name), 

5959 ) 

5960 return cleanup 

5961 

5962 def _create_or_update_hpa(self, name: str, namespace: str, spec: dict[str, Any]) -> None: 

5963 """Create or update a Horizontal Pod Autoscaler for an inference endpoint.""" 

5964 autoscaling_config = spec.get("autoscaling", {}) 

5965 if not autoscaling_config.get("enabled"): 

5966 return 

5967 

5968 min_replicas = autoscaling_config.get("min_replicas", 1) 

5969 max_replicas = autoscaling_config.get("max_replicas", 10) 

5970 metrics_config = autoscaling_config.get("metrics", [{"type": "cpu", "target": 70}]) 

5971 

5972 self._apply_hpa( 

5973 hpa_name=name, 

5974 namespace=namespace, 

5975 target_name=name, 

5976 min_replicas=min_replicas, 

5977 max_replicas=max_replicas, 

5978 metrics_config=metrics_config, 

5979 ) 

5980 

5981 @staticmethod 

5982 def _role_autoscaling_config( 

5983 spec: dict[str, Any], 

5984 role: str, 

5985 ) -> dict[str, Any] | None: 

5986 """Return an enabled role config, or ``None`` for static ownership.""" 

5987 mooncake = spec.get("mooncake") 

5988 if not isinstance(mooncake, dict): 

5989 return None 

5990 autoscaling = mooncake.get("autoscaling") 

5991 if not isinstance(autoscaling, dict) or autoscaling.get("enabled") is not True: 

5992 return None 

5993 role_config = autoscaling.get(role) 

5994 return role_config if isinstance(role_config, dict) else None 

5995 

5996 def _reconcile_role_autoscaler( 

5997 self, 

5998 name: str, 

5999 namespace: str, 

6000 spec: dict[str, Any], 

6001 role: str, 

6002 *, 

6003 apply_desired: bool = True, 

6004 ) -> ResourceCleanupResult: 

6005 """Converge one Mooncake role to exactly one or zero replica owners.""" 

6006 target_name = f"{name}-{role}" 

6007 role_config = self._role_autoscaling_config(spec, role) 

6008 if role_config is None: 

6009 return self._delete_autoscalers( 

6010 (target_name,), 

6011 (target_name, f"keda-hpa-{target_name}"), 

6012 namespace, 

6013 ) 

6014 

6015 metrics_config = role_config.get("metrics", [{"type": "cpu", "target": 70}]) 

6016 if self._metrics_require_keda(metrics_config): 

6017 cleanup = self._delete_hpas((target_name,), namespace) 

6018 else: 

6019 cleanup = self._merge_cleanup_results( 

6020 self._delete_scaled_objects((target_name,), namespace), 

6021 self._delete_hpas((f"keda-hpa-{target_name}",), namespace), 

6022 ) 

6023 if cleanup.complete and apply_desired: 

6024 self._create_role_hpa(name, namespace, spec, role) 

6025 return cleanup 

6026 

6027 def _create_role_hpa(self, name: str, ns: str, spec: dict[str, Any], role: str) -> None: 

6028 """Create or update one autoscaler for a single Mooncake role. 

6029 

6030 When the endpoint's ``mooncake.autoscaling`` block is enabled and 

6031 carries a config for this role, this materializes exactly one autoscaler 

6032 named ``{name}-{role}`` that scales the matching ``{name}-{role}`` 

6033 Deployment between the role's ``min_replicas`` and ``max_replicas``. The 

6034 role Deployment itself is already materialized at ``min_replicas`` by 

6035 :meth:`_replica_count_for_role`, so the autoscaler owns the count from 

6036 that lower bound. When autoscaling is absent or disabled, or when the 

6037 role carries no config, no autoscaler is created and the role's replicas 

6038 stay at their topology value. 

6039 

6040 Args: 

6041 name: The endpoint name. 

6042 ns: The namespace the role Deployment lives in. 

6043 spec: The endpoint spec; ``spec["mooncake"]["autoscaling"]`` drives 

6044 the bounds and metrics. 

6045 role: One of ``"prefill"`` or ``"decode"``. 

6046 """ 

6047 role_cfg = self._role_autoscaling_config(spec, role) 

6048 if role_cfg is None: 

6049 return 

6050 

6051 min_replicas = role_cfg.get("min_replicas", 1) 

6052 max_replicas = role_cfg.get("max_replicas", 10) 

6053 metrics_config = role_cfg.get("metrics", [{"type": "cpu", "target": 70}]) 

6054 

6055 target_name = f"{name}-{role}" 

6056 self._apply_hpa( 

6057 hpa_name=target_name, 

6058 namespace=ns, 

6059 target_name=target_name, 

6060 min_replicas=min_replicas, 

6061 max_replicas=max_replicas, 

6062 metrics_config=metrics_config, 

6063 ) 

6064 

6065 # ------------------------------------------------------------------ 

6066 # Metrics 

6067 # ------------------------------------------------------------------ 

6068 

6069 def get_metrics(self) -> dict[str, Any]: 

6070 return { 

6071 "cluster_id": self.cluster_id, 

6072 "region": self.region, 

6073 "running": self._running, 

6074 "reconcile_count": self._reconcile_count, 

6075 "errors_count": self._errors_count, 

6076 "seconds_since_last_pass": time.monotonic() - self._last_loop_completed_at, 

6077 } 

6078 

6079 

6080def create_inference_monitor_from_env() -> InferenceMonitor: 

6081 """Create an InferenceMonitor from environment variables.""" 

6082 cluster_id = os.getenv("CLUSTER_NAME", "unknown-cluster") 

6083 region = os.getenv("REGION", "unknown-region") 

6084 namespace = os.getenv("INFERENCE_NAMESPACE", "gco-inference") 

6085 interval = int(os.getenv("RECONCILE_INTERVAL_SECONDS", "15")) 

6086 

6087 # Enable structured JSON logging for CloudWatch Insights 

6088 configure_structured_logging( 

6089 service_name="inference-monitor", 

6090 cluster_id=cluster_id, 

6091 region=region, 

6092 ) 

6093 

6094 store = InferenceEndpointStore() # Uses DYNAMODB_REGION env var, falls back to REGION 

6095 

6096 return InferenceMonitor( 

6097 cluster_id=cluster_id, 

6098 region=region, 

6099 store=store, 

6100 namespace=namespace, 

6101 reconcile_interval=interval, 

6102 ) 

6103 

6104 

6105async def main() -> None: 

6106 """Entry point for the inference monitor.""" 

6107 monitor = create_inference_monitor_from_env() 

6108 logger.info("Inference monitor initialized: %s", monitor.get_metrics()) 

6109 

6110 # Expose Prometheus metrics on a dedicated port for the in-cluster 

6111 # observability scrape. A scrape-time collector reflects the monitor's live 

6112 # counters (reconcile_count, errors_count, running), so no push from the 

6113 # reconcile loop is needed. 

6114 from gco.services.service_metrics import start_metrics_server 

6115 

6116 metrics_port = int(os.getenv("METRICS_PORT", "9090")) 

6117 start_metrics_server(metrics_port, "inference-monitor", monitor.get_metrics) 

6118 

6119 # Kubernetes stops pods with SIGTERM. This process is PID 1 in its 

6120 # container, and PID 1 receives no kernel-default signal handling — so 

6121 # without an explicit handler SIGTERM was silently ignored, every pod 

6122 # rotation burned the full terminationGracePeriodSeconds, and the kubelet 

6123 # SIGKILLed the monitor mid-reconcile (exit 137). The handler flips the 

6124 # same stop flag the reconcile loop already honors, so shutdown waits for 

6125 # the in-flight cycle and exits 0 within one reconcile interval. 

6126 shutdown_requested = asyncio.Event() 

6127 loop = asyncio.get_running_loop() 

6128 

6129 def _handle_sigterm() -> None: 

6130 logger.info("SIGTERM received; stopping inference monitor after current cycle") 

6131 shutdown_requested.set() 

6132 monitor.stop() 

6133 

6134 try: 

6135 loop.add_signal_handler(signal.SIGTERM, _handle_sigterm) 

6136 except NotImplementedError: 

6137 # Non-POSIX event loops (Windows dev environments running the unit 

6138 # suite) don't support loop signal handlers; in the Linux container 

6139 # this always succeeds. 

6140 logger.debug("Event loop does not support signal handlers; skipping SIGTERM hook") 

6141 

6142 try: 

6143 while not shutdown_requested.is_set(): 

6144 try: 

6145 await monitor.start() 

6146 except KeyboardInterrupt: 

6147 logger.info("Shutting down inference monitor") 

6148 monitor.stop() 

6149 break 

6150 except Exception as e: 

6151 logger.error("Monitor crashed, restarting in 10s: %s", e, exc_info=True) 

6152 monitor.stop() 

6153 monitor._running = False 

6154 await asyncio.sleep(10) 

6155 logger.info("Inference monitor exited cleanly") 

6156 finally: 

6157 with contextlib.suppress(NotImplementedError, ValueError): 

6158 loop.remove_signal_handler(signal.SIGTERM) 

6159 

6160 

6161if __name__ == "__main__": 

6162 asyncio.run(main())