openalice-scheduler
[Atlas card](https://atlas.blal.pro/repos/openalice-scheduler)
Architecture rating
Adopts openalice-axum-common substrate (cors_from_env_or_default, verify_internal_secret, MetricsBuilder) matching sibling services; clean GpuProvider trait + ordered-policy abstraction, zero cycles. Minor env drift: CORS_ORIGINS + RUST_LOG declared-unused (drift 80).
/health + /metrics (Prometheus via shared MetricsBuilder), #[tracing::instrument] on handlers, TraceLayer, opt-in OTLP→Jaeger/Tempo with service.name/version and logs-only fallback. Strong, but metrics are 3 bare gauges — no provision-latency/failure counters or histograms.
Valid manifest (100), 17 features, dense @feature annotations + rich in-code architecture comments (sweeper/two-phase release/OTLP rationale), AGENTS.md present. Dragged down by a thin README (just an Atlas-card link) and 61 missing docstrings flagged by inspector.
Every /v1 endpoint gated by shared verify_internal_secret (X-Internal-Secret); release adds X-Lease-Owner 403 ownership check; CORS from env; secret required-or-bail at boot. No rate-limiting, binds 0.0.0.0, no security headers; low injection/SSRF surface (provisions to fixed RunPod/Docker only).
RunPod client 30s timeout, 30s TTL sweeper reaps expired leases idempotently, release tolerates already-gone (404), provision maps to 502/503. But NO graceful shutdown on SIGTERM (axum::serve, no drain), no retries on transient RunPod/Docker errors, and in-memory registry orphans cloud pods on crash/restart.
6 integration tests on stub provider cover policy first-available, registry round-trip, TTL sweep, and owner_id propagation — but zero coverage of HTTP handlers: the verify_internal_secret gate and the 403 owner-mismatch path are untested, and RunPod/local-docker drivers have no tests (avg complexity 2.21).
No datastore at all — leases held in in-memory Arc<RwLock<HashMap>>; manifest owns zero tables, no migrations/persistence/RLS/GDPR. owner_id tag is the only data discipline; state (and paid-pod tracking) is lost on restart by design but axis-weak.
source · auto-grader-2026-06-16
Ecosystem manifest
.atlas-deps.ymlGPU/compute lease service — acquire/release/list workers from local-Docker or RunPod backends for renderer and anim workloads.
- via cargoShared axum plumbing: verify_internal_secret, cors_from_env_or_default, MetricsBuilder.
- via INTERNAL_SECRETanim service calls POST /v1/lease + DELETE /v1/lease/{id} to provision and release anim workers.
- via INTERNAL_SECRETlive-control calls POST /v1/lease + DELETE /v1/lease/{id} to provision and release renderer workers.
- INTERNAL_SECRET
- SCHEDULER_PORT
- PROVIDERS
- RENDERER_IMAGE
- ANIM_IMAGE
- DOCKER_NETWORK
- RUNPOD_API_KEY
- RUNPOD_RENDERER_TEMPLATE
- RUNPOD_ANIM_TEMPLATE
- OTEL_EXPORTER_OTLP_ENDPOINT
- OTEL_SERVICE_NAME
- RUST_LOG
- CORS_ORIGINS
- docker_image: openalice-scheduler
- http_endpoint: POST /v1/lease
- http_endpoint: DELETE /v1/lease/{lease_id}
- http_endpoint: GET /v1/leases
- http_endpoint: GET /health
- http_endpoint: GET /metrics
Atlas grepped the repo's source for env reads (rust env::var · ts process.env · py os.environ · shell $VAR) and diffed against the manifest's consumes_env. Two lists below: stale declarations and undeclared reads.
- CORS_ORIGINS
- RUST_LOG
Roadmap · what's planned next
- runpod-onlineplanned12%Take the RunPod driver out of skeleton status — drop in a real RUNPOD_API_KEY + template ids, validate podFindAndDeployOnDemand provisioning end-to-end against a live renderer broadcast, then flip PROVIDERS to include `runpod`.
- persistent-registryplanned9%Replace the in-process lease registry with a durable store (Postgres or Redis) so a scheduler restart doesn't lose active leases and the service becomes horizontally scalable.
- cheapest-first-policyplanned11%Extend policy.rs beyond ordered-first-available — score providers by hourly cost + region + current load and pick the cheapest live option, with bin-packing across active leases on the same backend.
- extra-providers-modal-fly-vastplanned8%Land additional GpuProvider drivers (Modal, Fly.io GPU, vast.ai) so the scheduler can fail over across cloud vendors when one runs out of capacity.
- leases-list-paginationplanned7%Paginate GET /v1/leases + add filter by kind/owner_id so operator tooling stays usable once steady-state concurrent leases exceed ~50.
Code composition
tokei · loc- Rust96.7%
- TOML3.3%
- Rust96.7%
- TOML3.3%
- Rust96.7%· 1,024
- TOML3.3%· 35
| Language | % code | code | comments | blanks | files |
|---|---|---|---|---|---|
| Rust | 96.7% | 1,024 | 39 | 106 | 9 |
| TOML | 3.3% | 35 | 5 | 4 | 1 |
| Markdown | 0.0% | 0 | 98 | 45 | 2 |
Telemetry ribbon · last 90 days
12 eventsscheduler/api
3 features- scheduler.api.acquiresrc/main.rs:257POST /v1/lease — X-Internal-Secret gated. Selects the first available provider via `OrderedFirstAvailable` policy, calls `provider.provision(req)`, records the resulting `Lease` in the in-memory `LeaseRegistry`, and returns `{ lease, provider }`. Returns 503 if no provider can satisfy the request, 502 on provision failure. [tracing::instrument(
- scheduler.api.listsrc/main.rs:381GET /v1/leases — X-Internal-Secret gated. Returns the full snapshot of active `Lease` objects held in the in-memory registry at the moment of the call. Used by live-control and ops tooling to inspect pool occupancy.
- scheduler.api.releasesrc/main.rs:308DELETE /v1/lease/{lease_id} — X-Internal-Secret gated. Verifies `X-Lease-Owner` when `Lease.owner_id` is set (403 on mismatch). Atomically removes the lease from the registry via `registry.take`, calls `provider.release(lease)` to stop + remove the container (Docker) or terminate the pod (RunPod), and returns 204 No Content. Returns 404 if the lease is not found. [tracing::instrument(
scheduler/health
1 feature- scheduler.health.endpointsrc/main.rs:394GET /health returns `{status:"ok", service:"openalice-scheduler"}` — Traefik liveness probe; un-gated so the probe doesn't depend on the internal secret being mounted into the load balancer.api:GET /healthsince 0.1.0
scheduler/metrics
1 feature- scheduler.metrics.endpointsrc/main.rs:408GET /metrics — Prometheus text with `scheduler_up`, `scheduler_active_leases` (current registry size), and `scheduler_providers` (configured driver count); built via openalice-axum-common's MetricsBuilder for parity with sibling services.api:GET /metricssince 0.1.0
scheduler/policy
1 feature- scheduler.policy.ordered-first-availablesrc/policy.rs:9Ordered first-available provider selection — iterates declared providers in PROVIDERS order and picks the first whose `available(req)` returns true. Cheapest-first / geo-aware revs are roadmap.since 0.1.0
scheduler/provider
3 features- scheduler.provider.local-dockersrc/providers/local_docker.rs:12Default GpuProvider — spawns renderer/anim containers via bollard on the host Docker engine, attaches them to DOCKER_NETWORK, and tears them down on release. Handles gpu=none + gpu=entry; the free-tier path.since 0.1.0
- scheduler.provider.runpodsrc/providers/runpod.rs:11RunPod GraphQL driver — provisions renderer/anim pods via podFindAndDeployOnDemand (non-interruptable) and releases via podTerminate. Activated when `runpod` is listed in PROVIDERS and RUNPOD_API_KEY is set.since 0.1.0
- scheduler.provider.traitsrc/provider.rs:11`GpuProvider` defines the two-method contract (`provision` + `release`) all backends must implement plus a cheap `available` probe used by the policy layer. Public types `LeaseRequest`, `Lease`, `WorkerKind` (`renderer` | `anim`), and `GpuClass` (`none` | `entry` | `mid` | `high`) are the only wire types the scheduler API exposes; callers and providers share nothing else.
scheduler/providers
1 feature- scheduler.providers.buildsrc/main.rs:200build_providers reads the `PROVIDERS` env (comma-separated names, default "local-docker"), instantiates each driver in declared order, and returns them as `Vec<Arc<dyn GpuProvider>>` for the policy layer; bails when an unknown name is listed or no driver is configured.since 0.1.0
scheduler/registry
3 features- scheduler.registry.spawn-sweepersrc/leases.rs:116spawn_sweeper boots the 30-second TTL reaper Tokio task; first tick is dropped so the sweep only runs on the periodic cadence, not at startup before any lease exists.since 0.1.0
- scheduler.registry.storesrc/leases.rs:28LeaseRegistry — in-memory `Arc<RwLock<HashMap<lease_id, ActiveLease>>>` shared across HTTP handlers + the sweeper task; `record` / `take` / `list` / `count` are the only mutation/read surface so consistency between provider drivers and the API stays tight.type:LeaseRegistrysince 0.1.0
- scheduler.registry.sweepsrc/leases.rs:10Background TTL sweeper spawned at startup via `spawn_sweeper`. Every 30 seconds `sweep_once` reads expired leases (expires_at ≤ now), atomically removes each entry, and calls `provider.release(lease)` on the owning driver. A failed release is logged as a warning; the entry is still removed so the registry stays consistent. Bounds cloud costs on crashed callers.
scheduler/service
1 feature- scheduler.service.cratesrc/lib.rs:5GPU/compute lease service for OpenAlice. Exposes a uniform HTTP API (POST /v1/lease, DELETE /v1/lease/{id}, GET /v1/leases) so upstream services (openalice-live-control, openalice-anim) provision and release renderer + anim workers without caring which backend (local-docker, RunPod, Modal, …) runs the work. Lifecycle: provision → optional heartbeat via TTL → explicit release or 30 s sweep auto-reap.
scheduler/tracing
1 feature- scheduler.tracing.otlpsrc/main.rs:67init_otel_tracer wires an OTLP gRPC span exporter (opt-in via `OTEL_EXPORTER_OTLP_ENDPOINT`) and tags every span with `service.name` + `service.version` so traces land in Jaeger/Tempo under the right service without per-deploy config; failure falls back to logs-only.since 0.1.0
scheduler/types
2 features- scheduler.types.leasesrc/provider.rs:121Lease wire shape — { lease_id, provider, kind, endpoint, created_at, expires_at, provider_token, owner_id }; `provider_token` is the opaque id (container id / RunPod pod_id) the driver needs to release.type:Leasesince 0.1.0
- scheduler.types.lease-requestsrc/provider.rs:57LeaseRequest wire shape — { kind (renderer|anim), gpu (none|entry|mid|high), correlation_id, owner_id, region, ttl_seconds (≤8h), env map }; default ttl=3600, default gpu=None; carries `owner_id` so release can enforce X-Lease-Owner.type:LeaseRequestsince 0.1.0