Coverage for gco / resource_governance.py: 100.00%
22 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-09-14 22:07 +0000
1"""Shared gco-jobs resource-governance defaults and quantity parsing.
3Three enforcement layers govern job resources and must tell one story: the
4manifest/queue processors cap what a single submitted manifest may total,
5the gco-jobs ``LimitRange`` caps each container, and the namespace
6``ResourceQuota`` caps the aggregate. The values below are the single source
7of truth for all three, shared by:
9- the regional stack (substitutes them into ``04-resource-quotas.yaml`` and
10 validates cdk.json overrides against the layering invariant at synth),
11- the manifest and queue processors (their built-in runtime defaults), and
12- the example-job validation static checks (prove every shipped example
13 fits these defaults offline).
15This module lives at the top of the ``gco`` package — NOT under
16``gco.stacks`` — because the service container images deliberately exclude
17``gco/stacks/**`` from their build context (synth-only code must never
18rebuild service images; see ``_SERVICE_IMAGE_COMMON_EXCLUDES`` in the
19regional stack). A runtime import from ``gco.stacks`` fails the distroless
20runtime smoke at image build. ``gco.stacks.constants`` re-exports these
21names for synth-side callers.
22"""
24from __future__ import annotations
26from collections.abc import Mapping
27from types import MappingProxyType
29DEFAULT_RESOURCE_QUOTA: Mapping[str, str] = MappingProxyType(
30 {
31 # Namespace-wide aggregate ceilings (ResourceQuota on requests.*):
32 # sized to hold two full accelerator-node training pods side by side
33 # with CPU headroom for the surrounding jobs.
34 "max_cpu": "400",
35 "max_memory": "4096Gi",
36 "max_gpu": "32",
37 "max_pods": "50",
38 # Per-container ceilings (LimitRange max): one full accelerator-node
39 # slice — p5.48xlarge / trn2.48xlarge expose 192 vCPUs, 2 TiB memory,
40 # and 8 GPUs, and one-pod-per-node is the standard unit of
41 # distributed training. Anything smaller rejects the platform's own
42 # EFA training example at pod admission (observed live: example-job
43 # validation run ex241-df723811, where the previous 10-CPU/64Gi/4-GPU
44 # caps left the Job permanently podless with only namespace events
45 # explaining why).
46 "container_max_cpu": "192",
47 "container_max_memory": "2048Gi",
48 "container_max_gpu": "8",
49 }
50)
51"""Default ``resource_quota`` context for the gco-jobs namespace."""
54def parse_k8s_quantity(value: object) -> float:
55 """Parse a Kubernetes resource quantity into a float of base units.
57 Supports the quantity forms GCO's manifests actually use: bare integers
58 and decimals (``8``, ``0.5``), CPU millicores (``250m``), and the binary
59 and decimal suffixes (``Ki Mi Gi Ti Pi`` / ``k M G T P``).
61 Raises:
62 ValueError: If the value is not a parseable quantity.
63 """
64 text = str(value).strip()
65 if not text:
66 raise ValueError("empty resource quantity")
67 binary = {"Ki": 2**10, "Mi": 2**20, "Gi": 2**30, "Ti": 2**40, "Pi": 2**50}
68 decimal = {"k": 10**3, "M": 10**6, "G": 10**9, "T": 10**12, "P": 10**15}
69 for suffix, factor in binary.items():
70 if text.endswith(suffix):
71 return float(text[: -len(suffix)]) * factor
72 if text.endswith("m"):
73 return float(text[:-1]) / 1000.0
74 for suffix, factor in decimal.items():
75 if text.endswith(suffix):
76 return float(text[: -len(suffix)]) * factor
77 return float(text)
80DEFAULT_MANIFEST_RESOURCE_CAPS: Mapping[str, object] = MappingProxyType(
81 {
82 # Front-door budget for one API/SQS-submitted manifest, enforced by
83 # the manifest and queue processors before anything reaches the
84 # cluster: two full accelerator-node slices, i.e. the canonical
85 # two-node distributed-training manifest
86 # (examples/efa-distributed-training.yaml). Layering invariant,
87 # validated at synth: container_max_* (LimitRange) <= per-manifest
88 # cap <= max_* (namespace ResourceQuota) on every dimension — the
89 # front door must never reject a manifest whose pods the namespace
90 # would admit, and must never accept one it cannot possibly run.
91 "max_cpu_per_manifest": "384",
92 "max_memory_per_manifest": "4096Gi",
93 "max_gpu_per_manifest": 16,
94 }
95)
96"""Default ``job_validation_policy.resource_quotas`` for submitted manifests."""