Commit Graph

1964 Commits

Author SHA1 Message Date
Josh Siviour 3bb35deea2 fix: dial the ochre appliance customer ENI, not the management NIC (#851)
Both the endpoint ENI and the management NIC carry the rds-db-instance tag,
so a first-match pick non-deterministically dialed the unreachable management
NIC. Select the endpoint ENI by its RDS description, falling back to any usable
tagged NIC.
2026-08-21 14:03:59 +10:00
Josh Siviour 7bfeaf885c fix: give LB microVM initramfs headroom by bumping sys.micro to 256 MiB (#850)
The ELBv2 LB microVM boots with -m 128 (sys.micro). After kernel
reservations the 37.7 MiB initramfs — 45% of it the Go 1.27 lb-agent —
leaves under 512 KiB of headroom, so the guest panics with 'Initramfs
unpacking failed' before init and lb-agent never heartbeats.

Bump sys.micro to 256 MiB so the initramfs unpacks with margin, and add
an uncompressed-initramfs gate to build-microvm-image.sh (the compressed
gate was blind to the guest-RAM footprint) so a future bump fails the
build loudly instead of panicking in production.
2026-08-20 23:14:20 +10:00
Josh Siviour 3e4148cf15 fix: let a class-change modify recover a failed RDS instance whose agent is dead (#849)
A class-change ModifyDBInstance is the intended remedy for a failed DB
instance, but applyPendingModifications applied the parameter group through the
live instance agent first. A dead agent returned ErrCommandUnreachable and
aborted the modify before replaceInstanceVM could run, so the one recovery
lever the API offers could not run for the case that needs it most.

The replacement VM's fresh agent applies the resolved set itself on boot, so
the pre-replace apply against the old dead agent is redundant for a class
change. Tolerate ErrCommandUnreachable there: resolve the class-correct set
(a KV read, no agent), persist it for the fresh agent, and skip only the dead
agent's apply. Parameter-group-only modifies still fail on a dead agent.
2026-08-20 23:10:53 +10:00
Josh Siviour c941830664 feat: gate self-host inference behind capacity-based ThrottlingException (#848)
* feat: gate self-host inference behind capacity-based ThrottlingException

Add a process-scoped concurrency limiter keyed by (servingAccountID,
modelID) with capacity = catalog MaxConcurrency x max(ModelUnits, 1),
and apply it to every self-host Converse/ConverseStream/InvokeModel/
InvokeModelWithResponseStream path. Over-capacity requests reject
immediately with ThrottlingException instead of queuing inside vLLM,
matching real Bedrock. Managed (Anthropic) providers are untouched.

* test: mark admission_test as in-package for the unexported limiter

* test: cover capacity admission gate and throttling
2026-08-20 22:28:52 +10:00
Josh Siviour c918f6834a feat: back up the ochre pgvector schema to object storage (#847)
* feat: back up the ochre pgvector schema to object storage

Adds account-scoped pg_dump/restore of the per-account kb_<accountID>
schema to predastore, gzip-compressed and keyed under the account's
own prefix. Backup shells out to pg_dump (--no-owner --no-privileges
--clean --if-exists) behind a PgDumper seam; restore runs psql then
repairs the account role's grants via EnsureAccount/RegrantAccount
since the dump carries no ownership. Exposed as
`spx admin ochre vector backup/restore --account <id>`.

* test: cover ochre vector backup dump/restore and error paths
2026-08-20 22:28:25 +10:00
Julian Sommer f10c9434d1 fix: detect KV CAS conflicts on replicated buckets (#843) 2026-08-20 16:16:22 +10:00
Josh Siviour f9c84919ef feat: co-served GPU model bundles (vLLM + TEI) with family-aware reaper (#845)
* feat: co-served GPU model bundles (catalog, lifecycle, embed/rerank routing)

Serve the generative LLM, embedder and reranker as one co-resident GPU
bundle: EMBEDDING/RERANK catalog modalities + co-serve group, bundle-aware
admission (one GPU claim sized to summed member VRAM), single-flight one-VM
launch with a member->port EndpointRecord, DynamicEndpointResolver member
routing. Ochrevector embed/rerank/token-introspection resolve through the
resolver (static single-endpoint map retired); rerank degrades to plain KNN on
absent/failure. Public InvokeModel-embeddings and top-level Rerank deferred.

* feat: co-serve vLLM+TEI bundle image with per-engine readiness

Replace the single-model vllm-serve unit with a generic bedrock-serve@
systemd template driven by per-member BEDROCK_* env files, dispatching to
vLLM or text-embeddings-router by engine and binding each member to its own
port and weights device. TEI serves directly on its serve port; readiness
probes each member on the route its engine answers (vLLM /v1/models, TEI
/health) with the engine family carried end-to-end from the catalog.

* fix: serve nomic embedder correctly in the co-serve bundle

Bound embeddings requests to the TEI client batch limit so a large
document's chunks are split across requests instead of 422ing. Serve
nomic with explicit mean pooling, since the flat weights layout omits
1_Pooling/config.json and TEI would otherwise default to CLS. Add
bge-base-en-v1.5 as a standalone alternative embedder.

* fix: make the bedrock reaper family-aware and pin-safe

A pinned endpoint is now exempt from terminate-and-relaunch on scrape
failure, mirroring how it is already exempt from idle reclaim, so a
transient unreachability can no longer tear down operator- or
commitment-managed serving.

A bundle with no generative vLLM member exposes no vLLM load metrics, so
it is liveness-probed on /health and kept warm rather than scraped for
idleness on a route it does not serve and reaped in a loop.

* test: lock in co-serve group-member convergence to one VM

* fix: pin self-host HF repo/revision in catalog and stop nested config flattening

Carry HFRepo/HFRevision per self-host catalog entry; weights pull defaults
--hf-repo and --revision from them so a bare pull cannot silently grab a
regressed upstream main (the nomic v5 config.json duplicate-field break).
Restrict fixed config filenames to the repo root so a nested config.json
(sentence-transformers 1_Pooling/) is dropped instead of flattening onto the
model config, and refuse any two source paths that collide at one dest key.

* fix: let endpoint delete clear an account-scoped pinned record and report honestly

A bare 'endpoint delete' resolved the Global key, which never held a pinned,
account-scoped record, then printed 'torn down' over the idempotent no-op while
the record survived. Add --account to the delete command so an operator can
target the account 'endpoint list' shows, and return DeleteEndpointOutput.Removed
so the CLI names a real teardown versus a lookup that found nothing and points
at --account. A gone VM already tolerates NotFound, so a scoped delete clears a
record whose instance was terminated out of band.
2026-08-20 16:08:55 +10:00
Josh Siviour 8eb3e4f879 fix: stop emitting an empty contentBlockStart union for text in ConverseStream (#844)
Real Bedrock emits contentBlockStart only for toolUse blocks; text flows
messageStart -> contentBlockDelta -> contentBlockStop -> messageStop. An
empty start:{} union violates Smithy's exactly-one-member rule and breaks
strict SDK stream consumers (boto3 errors, the AWS Go SDK stalls).
2026-08-20 15:50:38 +10:00
Julian Sommer d4d8fefae6 fix: join the drift loop goroutine before restoring the test timing vars
The drift loop tests launched DriftLoop with a bare go statement and never
joined it, so shrinkDriftTiming's cleanup wrote DriftInterval and
driftBackoffBase while the loop was still reading them in nextDriftBackoff.
Cleanups run LIFO, so registering the join after shrinkDriftTiming makes it
run first and closes the window.
2026-08-20 14:36:49 +10:00
Julian Sommer f765119515 fix: make instance-profile role and tag writes atomic (#842) 2026-08-20 14:32:38 +10:00
Julian Sommer 46f953698f fix: requeue an incomplete reconcile pass instead of waiting a drift interval (#840) 2026-08-20 13:34:39 +10:00
Julian Sommer 7b1890eb8a refactor: use the stdlib uuid package instead of github.com/google/uuid (#841) 2026-08-20 11:57:23 +10:00
Julian Sommer adb60963c3 fix: back QMP test mocks with a unix socket instead of net.Pipe
Go 1.27's encoding/json v2 decoder stops at the closing brace and leaves
Encoder.Encode's trailing newline unread. net.Pipe is unbuffered and
synchronous, so the writer blocks on that undelivered byte while the peer
blocks writing its reply, deadlocking on the first QMP command.

TestTryBlockdevDel hung until the 180s package timeout. A real unix socket
is kernel-buffered and matches production QMP transport, which is already
the pattern newMockQMPClientWithEvents uses.
2026-08-20 11:43:58 +10:00
Julian Sommer 74117e5b26 fix: remove the broken golangci-lint cache path 2026-08-20 11:37:10 +10:00
Julian Sommer 8e01e76dc0 fix: disable embedlit 2026-08-20 10:52:03 +10:00
Julian Sommer e36c8f6ba8 chore: update to go 1.27 2026-08-20 10:37:52 +10:00
Julian Sommer 98365d5554 fix: wait for the upstream release in the stale-lease DHCP test
The replacement lease is persisted before releaseStaleUpstream runs, so
the KV predicate did not prove the release had completed and the count
assertion could observe zero under race-detector scheduling.
2026-08-20 10:01:11 +10:00
Julian Sommer c84200e519 feat: probe real qemu state (#839) 2026-08-20 09:46:53 +10:00
Josh Siviour c5a4bdab75 fix: attach to leader KV buckets before creating them (#837)
The RDS reconciler and the ECS scheduler call InitLeaderBucket on every
tick, and it created before attaching. CreateKeyValue against a bucket that
already exists is only a no-op when the stored config matches the requested
one; otherwise JetStream answers 10058 and the caller silently falls back to
an attach.

On a multi-node cluster it never matches. The stored buckets are replicated
while the RDS and ECS calls ask for the default replica count, so every tick
billed an error. That is the whole of the 14-18% JetStream API error rate
seen on env1 and env19, and it landed entirely on the meta leader because
that is the node that serves STREAM.CREATE. Single-node clusters matched and
so sat at 0%, which is why this never showed up in CI.

Attaching first removes the per-tick create outright, so it no longer matters
whether the configs agree. EKS already passed a replica count and so did not
error, but it made the same wasted call every tick and is changed with them;
its replica count now applies to a genuine first creation, which is the only
time it was ever read.

The regression test asserts on the API traffic rather than the return value.
Both orders reach the same handle, so idempotency cannot tell them apart, and
on a single-node test server the old order raised no error at all - it just
issued the create. Watching for the STREAM.CREATE advisory catches it there.
2026-08-20 09:33:34 +10:00
Julian Sommer 7f6557d55d fix: make state retry policy injectable (#836) 2026-08-20 09:25:17 +10:00
Julian Sommer 8f680abcde fix: don't show system owned images in run instances ui 2026-08-20 09:08:24 +10:00
Ben Duncan f32123b6c9 Sandbox env reference 2026-08-19 14:45:52 -05:00
Ben Duncan aaac8fbe1c feat: per-account service quotas with an admin override surface
Adds per-account resolution of the [quota] limits from a KV override record,
an admin surface and CLI to set one, RDS and load-balancer dimensions, and an
e2e suite that drives every gate on single and multi node.

Also fixes external IPAM initialising without retry: a node that lost a race
against JetStream convergence silently served the disabled EIP stub for the
life of the process, answering cluster-wide with an empty address list.
2026-08-20 04:07:40 +10:00
Ben Duncan 4dadce8ed5 test: cover the external pool projection fed to IPAM 2026-08-20 04:07:19 +10:00
Ben Duncan ae2a1830ef Merge remote-tracking branch 'origin/dev' into account-quota 2026-08-20 04:01:14 +10:00
Ben Duncan cfb0abc8a8 chore: drop a local settings temp file committed by mistake 2026-08-20 04:01:11 +10:00
Ben Duncan 4508193180 fix: fail daemon start when a node with public pools cannot build external IPAM 2026-08-20 03:48:45 +10:00
Ben Duncan 0d8425ef51 test: skip the EIP quota leg where a cell does not serve EIPs from every node 2026-08-20 03:31:40 +10:00
Ben Duncan 19ec127e2a test: charge a multi-instance launch for every instance it asks for 2026-08-20 02:43:44 +10:00
Ben Duncan f41f5530a0 test: add an e2e suite proving quotas gate every capped resource 2026-08-20 02:26:27 +10:00
Ben Duncan 0f8471062f feat: add an admin surface for per-account quota overrides 2026-08-20 02:05:17 +10:00
Ben Duncan dca7a38356 feat: resolve service quotas per account from a KV override record 2026-08-20 01:52:07 +10:00
Julian Sommer fcde6b8de3 fix: authorize RDS snapshot source and target (#835) 2026-08-19 15:36:20 +10:00
Julian Sommer f21db26816 chore: block panic 2026-08-19 15:03:47 +10:00
Julian Sommer 60c9dfa89d fix: remove rds panics 2026-08-19 14:58:35 +10:00
Julian Sommer 941affcf3b fix: validate addon catalogue without panicking 2026-08-19 14:41:23 +10:00
Julian Sommer 6e1e2f3fbb fix: don't panic on managed policy json unmarshal failure 2026-08-19 14:39:39 +10:00
Julian Sommer 28330cb318 fix: dont panic on missing flags 2026-08-19 14:37:07 +10:00
Julian Sommer dede6c1158 fix: mock race 2026-08-19 14:13:21 +10:00
Julian Sommer cf2e259a30 fix: use 1024 bit certs in rds tests to speedup 30% 2026-08-19 14:01:13 +10:00
Julian Sommer 9f74594cb5 fix: invalidate backup queries after modify 2026-08-19 13:54:45 +10:00
Julian Sommer 6ef7dd8692 fix: name the missing RDS engine image instead of answering ServerInternal 2026-08-19 13:39:54 +10:00
Josh Siviour 52f12586f5 fix: let the tail sampler shed predastore traces instead of a head ratio (#834)
predastore pinned MULGA_ROOT_TRACE_RATIO=0.05 so guest block I/O, which is
locally rooted and has no caller trace, would not drown request traces. That
worked, but TraceIDRatioBased decides when the root span is created, before
any error exists, so it discarded 95% of predastore failures along with the
successes. An RCA on a rare locally-rooted failure had a 1-in-20 chance of
having the span at all.

The sink now runs a tail-sampling tier in front of APM Server that keeps every
errored trace, keeps every trace slower than 2s, and samples the rest at 10%.
It judges a trace once it is whole, so it can shed the same volume while
keeping the traces worth having. Dropping the head ratio is what lets it see
them: at 0.05 the tail sampler only ever saw 5% of predastore roots, so its
error rule could keep at most 5% of the failures.

Sized before changing: predastore's true local root rate is ~434 spans/s
across six active CI environments, against 21.7/s reaching the sink today.
Driving the tier at 508 spans/s held it at 88 MiB of a 1.5 GiB cap and 1.8%
CPU with zero drops, and trace storage goes from 14.9 to 22.5 GB per 7 days
on a volume with 735 GB free.
2026-08-19 13:25:45 +10:00
Josh Siviour 67425c41a0 docs: document the frontmatter length rules for the docs site (#832)
* docs: bring doc titles and descriptions into SEO length limits

Rewrite the frontmatter title and description of every published doc so the
rendered <title> lands in 50-60 characters (including the site suffix) and the
meta description lands in 150-160 characters.

* docs: move SEO titles to a seoTitle field and restore display titles

The frontmatter title renders as the page H1 and the sidebar label, so tuning it
for the title tag shortened nothing and lengthened the visible navigation. Put
the search-facing string in seoTitle and give title back its original value.

* docs: document the frontmatter length rules for the docs site
2026-08-19 12:24:15 +10:00
Julian Sommer 3ee412ca02 fix: keep the RDS endpoint ENI alive across a DB instance replace (#833) 2026-08-19 12:18:04 +10:00
Julian Sommer 894fe84255 test: dump OVN MAC_Binding and route state when natuplink egress fails
The artifact bundle carried the guest console and the spinifex journals but
nothing from OVN, so a phase 6 egress failure could not be attributed to an
unresolved gateway nexthop MAC versus a return-path or upstream problem.

Register an OnFailure at the top of phase 6 that writes
static-mac-binding-list, the SB MAC_Binding table and the VPC router's
lr-route-list into the bundle.
2026-08-19 12:08:38 +10:00
Julian Sommer 7eff416c25 fix: collect IPsec and OVN datapath logs in the nightly e2e bundle
The nightly collected only spinifex-* journal units, so a cross-chassis
black-hole could not be attributed to IPsec SA establishment versus SB
port binding. ovn-controller, ovn-northd and ovs-vswitchd log to files
rather than journald, so they were absent from the bundle entirely.

Adds the openvswitch-ipsec.service journal per node, mirroring e2e.yml,
and pulls /var/log/ovn and /var/log/openvswitch off each node.
2026-08-19 12:06:35 +10:00
Josh Siviour e84753f9b9 docs: bring doc titles and descriptions into SEO length limits (#831)
* docs: bring doc titles and descriptions into SEO length limits

Rewrite the frontmatter title and description of every published doc so the
rendered <title> lands in 50-60 characters (including the site suffix) and the
meta description lands in 150-160 characters.

* docs: move SEO titles to a seoTitle field and restore display titles

The frontmatter title renders as the page H1 and the sidebar label, so tuning it
for the title tag shortened nothing and lengthened the visible navigation. Put
the search-facing string in seoTitle and give title back its original value.
2026-08-19 12:05:59 +10:00
Julian Sommer a8d0ba185e fix: bound the natuplink guest egress loop to the console verdict budget
The guest fail-loop ran 36 iterations at ~28 s each (two 10 s curl timeouts,
a 3 s ping, a 5 s sleep), so NAT-E2E-EGRESS-FAIL first printed at ~16m50s
while the console wait gave up at 8m. The marker was structurally
unreachable and an egress failure reported only a byte count.

Derive the wait budget from the loop instead of hard-coding both ends: 10
iterations at a 30 s per-iteration allowance plus 3m for boot and cloud-init,
keeping the same 8m ceiling. The user-data script is now generated from those
constants and the existing marker consts. Dump the last console read via
OnFailure so a budget overrun also leaves the console in artifacts.
2026-08-19 12:02:31 +10:00
Julian Sommer 17cea6cd4b fix: gate the SG datapath probe on the target's HTTP listener
TestSGPolicyDatapath/AllowedTraffic waited 60s for curl to reach the
target's HTTP server but nothing ever waited for that server to start.
Its cloud-init runs later than the client's sshd, which is what the
preceding stage actually gates on, so the budget was measured from the
wrong baseline and the gap between the two is unbounded on a loaded
node. The failures reported curl exit 7, a refused connection: a
missing SG allow is a drop ACL here, which would time out as exit 28.

The target guest now reports its listener on the serial console, the
only channel back to a guest whose SG has no SSH ingress, and a new
TargetHTTPReady stage waits for that marker before AllowedTraffic runs
at all. AllowedTraffic keeps its 60s budget, which now only has to
cover chassis flow install, and dumps VPC flow diagnostics when it does
fail so a genuine propagation failure is attributable from CI artifacts.
2026-08-19 11:59:02 +10:00