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.
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.
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.
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.
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.
* 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
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.
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.
* 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.
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.
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.
test-package-check fails on dev without it: the test mutates the
unexported guestPortBackoffBase / guestPortBackoffMax and drives
ensureGuestPortDatapath and recordPortFailure directly.
A node whose NewExternalIPAM call lost the race with the JetStream meta
leader logs a WARN and carries on with d.externalIPAM nil. That nil
concrete pointer was handed straight to the PublicIPAllocator interface,
where it reads as non-nil, so the guard in PrepareRunInstances passed and
findPool derefed a nil receiver. RunInstances into a subnet with
MapPublicIpOnLaunch took the daemon down with SIGSEGV, and because the ENI
was already persisted and the gateway saw only a NATS timeout, nothing
rolled it back: the orphan ENI later rejected DeleteSecurityGroup with
DependencyViolation.
The concrete value now crosses into PublicIPAllocator and PublicIPReleaser
only once init succeeded, and AllocateIP/ReleaseIP tolerate a nil receiver
so another caller repeating the mistake degrades instead of panicking.
Making the interface genuinely nil is not enough on its own: the existing
nil check would then have skipped assignment altogether and booted an
instance with no public address on a subnet that promised one. The
wantPublic decision now runs unconditionally and a missing allocator takes
the allocation-failure path, which detaches and deletes the ENI, returns
capacity and reports InsufficientAddressCapacity.
Launches that do not ask for a public IP are untouched: subnets default to
MapPublicIpOnLaunch only when the cluster has public pools, which is the
same condition that initialises the IPAM.
* feat: ship per-test JUnit results to the Elasticsearch sink
Every e2e workflow already converts its test log to JUnit XML and renders
it into the job summary, then discards it with the artifact. mulga-ci-runs
records which suite failed; nothing records which test failed, so
root-causing a failure means reading raw Actions logs across historical
runs.
The junit-to-es action bulk-indexes one doc per leaf test case into
mulga-ci-test-results: name, subtest, status, duration, failure text, run
id, cell, suite, branch and sha. Parent t.Run wrappers are dropped so a
failing subtest counts once, matching the job summary renderer. The doc id
is deterministic, so a replayed post overwrites rather than
double-counting.
Called from every workflow that produces JUnit XML, and tolerated on
failure: an unreachable sink must not turn a green test run red.
* feat: classify unparsed JUnit cases as unknown, not error
go-junit-report emits <error message="No test result found"> when a
=== RUN line has no matching result line, which means the log it was fed
was truncated or filtered. Recording that as a failure would put 78
phantom failures per green bare-metal run into the pass rate.
Two ports on env19 have no tap behind them anywhere in the cluster:
their NB Logical_Switch_Port reads up: false and the SB Port_Binding
has chassis: [], so nothing can ever claim them. ensureGuestPortDatapath
is already bounded, but the outer reconcile loop calls it again every
cycle, so each dead port pays the full 45s nudge sequence and files
another ERROR roughly twelve times an hour, which is 1500 of vpcd's 1691
daily WARN lines and all of its ERRORs.
A port that burns its convergence deadline now backs off, doubling from
a minute to a thirty-minute cap, and clears the moment it binds. The
failure and recovery lines also carry the ENI, the EIP and the guest IP:
they previously named only the VPC and the LSP, so the guest behind a
stuck port could not be identified from telemetry.
slog renders a bare time.Duration as an int64 nanosecond count and names
the unit nowhere, so every duration field in the fleet read like
"elapsed":135000000000. The repo idiom of .Round(time.Second) changed
the value without changing the shape and truncated every sub-second
measurement to zero.
otelsetup.Millis converts a duration for logging under a snake_case key
ending in _ms, and TestSlogDurationConvention type-checks ./spinifex/...
and ./cmd/... to fail the build when a raw Duration reaches an slog call
or a Millis value is logged under a key without the suffix. It found 55
sites across 34 files, well past the sites a grep for time.Since shows,
because most durations come from a config field or a package constant.
A handler-level ReplaceAttr would fix every site at once but can only
emit d.String(), which Elasticsearch maps as a keyword and nothing can
aggregate over.