Skip to content

feat(quota): per-region per-machine-type VM quota guard on settings save + grow - #181

Open
JacobZuliani wants to merge 7 commits into
mainfrom
agent/02/quota-guard
Open

feat(quota): per-region per-machine-type VM quota guard on settings save + grow#181
JacobZuliani wants to merge 7 commits into
mainfrom
agent/02/quota-guard

Conversation

@JacobZuliani

@JacobZuliani JacobZuliani commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR turns GCP quota failures into clear, early feedback without storing quota state in Firestore.

  • Removes the cluster_quota Firestore document dependency and deletes the backfill script.
  • Reads live Google quota limits only at the two decision points that need them: settings save and node boot planning.
  • Uses Burla's active node state (BOOTING, READY, RUNNING) as current usage before booting a batch, so quota checks do not depend on Google quota-usage freshness.

User-visible quota scenarios

Settings save: selected hardware is impossible

Appears when the selected machine type, region, and quantity exceed the project's live GCP quota even before counting currently running Burla nodes. The settings are not saved. If some machines fit, the form is lowered to that count; if zero fit, the form returns to the last saved hardware config.

Settings quota error

Dashboard Start: only part of the boot batch fits

Appears when the saved settings are theoretically possible, but the current active Burla nodes plus the requested Start batch would exceed quota. Burla starts the machines that fit and shows this warning.

Dashboard start quota warning

Dashboard Start: none of the boot batch fits

Appears when the current active Burla nodes already consume the relevant quota, or the selected region has zero usable quota for the requested hardware. Burla does not schedule an impossible boot.

Dashboard start quota error

Python API grow=True: reduced scale

Appears when remote_parallel_map(..., grow=True) asks Burla to grow beyond the remaining quota. Burla boots the additional nodes that fit and prints the reduced-scale warning before continuing. The warning includes a self-hosting quota link: https://docs.cloud.google.com/docs/quotas/ .

Terminal quota warning

Python API grow=True: no requested nodes can boot

Appears when grow=True needs new nodes but none can be booted without exceeding quota, and there are no existing compatible nodes that can run the job. The client raises QuotaExceeded immediately.

Terminal quota error

Implementation notes

  • Settings save checks only absolute live limits, not current active Burla usage.
  • Node boot planning checks live limits minus Burla active usage immediately before scheduling the batch.
  • Dimensioned N4/H100/H200 limits come from Service Usage consumer quota metrics. Older A100 limits and regional instance quota come from Compute regions.get.
  • Dashboard Restart does not falsely count nodes it is about to delete against the replacement batch.
  • Actual GCE boot errors are still surfaced, because quota can race and capacity failures are separate from quota.

Verification

  • ./.venv/bin/python tests/test_quota.py
  • ./.venv/bin/python -m py_compile src/main_service/quota.py src/main_service/endpoints/client.py src/main_service/endpoints/settings.py src/main_service/endpoints/cluster_lifecycle.py
  • Live smoke: cap_boot_machine_types(['n4-standard-8'], 'us-central1', []) returned ['n4-standard-8'] with no warnings.
  • npm run build
  • npx eslint src/hooks/useClusterControl.ts src/hooks/useSaveSettings.ts src/pages/Index.tsx src/pages/Settings.tsx

Note: full npm run lint still fails on pre-existing unrelated lint errors outside these files.

…ave + grow

Blocks the two paths that could ask GCP to boot more VMs of a given
(region, machine_type) than quota allows:

- `POST /v1/settings` reads `cluster_quota/cluster_quota` and 400s with
  a structured `quota_exceeded` detail when `machineQuantity > limit`.
  Frontend catches it: caps quantity to `limit` when `limit >= 1`, or
  reverts machineType/gcpRegion/machineQuantity to the last-saved
  snapshot when `limit == 0`, and renders a "GCP Quota Limit Reached"
  dialog. Ported from PR #141's final quota-working revision.

- `POST /v1/jobs/{id}/start` with `grow=True` applies the same per-
  (region, machine_type) cap to the grow plan, counting existing
  active nodes against the limit. Over-quota types are soft-capped
  and their clip recorded in a `warnings` list on the response; the
  burla client prints those warnings and the job runs at reduced
  scale. If every machine type in the plan clips to zero, the endpoint
  400s with `quota_exceeded` and the client raises the new
  `QuotaExceeded` exception.

Missing (region, machine_type) entries read as unlimited so fresh
projects without a populated cluster_quota doc keep working. Local-dev
short-circuits both guards.

`scripts/backfill_cluster_quotas.py` enumerates every project
registered in `burla-prod/backend-service/clusters`, queries live GCP
Compute Engine per-region quotas, translates family-level CPU + GPU
quotas into per-machine-type VM counts, and writes the result to
`cluster_quota/cluster_quota` in each project's `burla` Firestore DB.
Idempotent with a `--dry-run` flag.

Checked offline against the module-level logic (get_quota_limit,
_apply_quota_cap) with NODES_CACHE + DB mocks. `make build-frontend`
runs clean. Backfill dry-run against burla-agent-02 produces valid
output using real compute.regions.get quotas.
The legacy compute v1 `RegionsClient.get().quotas` only surfaces A2_CPUS
and the NVIDIA_A100_* GPU metrics; N4, A3, H100, and H200 quotas are
served through the newer `cloudquotas` service under the combined
`CPUS-PER-VM-FAMILY-per-project-region` and `GPUS-PER-GPU-FAMILY-per-project-region`
entries. Without those, every n4-standard and a3-* row in the written
cluster_quota doc was always 0.

Script now hits both APIs and merges them: legacy for A2/A100, modern
for N4 (CPU-only) and H100/H200 (GPU-gated). Dry-run against
burla-agent-02 now correctly shows `n4-standard-2: 150`, etc., in
regions where N4 quota is granted.
Replace stored Firestore quota state with live Google quota checks at settings save and boot planning time so quota decisions cannot go stale.
Keep CPU grow quota caps from being dropped when no N4 machines can fit, so users see the intended warning or quota error.
Move N4 quota-aware packing into the shared boot planner so grow only describes desired capacity while boot planning owns quota capping and errors.
Remove unused quota helpers and return plain integer limits from Google quota reads.
# Conflicts:
#	main_service/src/main_service/endpoints/cluster_lifecycle.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant