mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
1afc7fa8aa
* fix: retry a recovery relaunch that lost a race with a starting dependency A guest drain-stopped by a coordinated cluster shutdown could be lost for good. The relaunch ran before JetStream was serving, the volume lease claim timed out, and the failure was recorded as permanent. The backoff this needed already existed. relaunchWithRetry retries five times, but only on ErrMountRetryable, and mountErrRetryable recognised exactly two viperblock state-load sentinels. A lease claim returning context deadline exceeded matched neither, so the mount was reported as permanently failed and the backoff never engaged once. - Classify a lease store that cannot answer as ErrLeaseStoreUnavailable, and include it in mountErrRetryable. A lease held by another node is deliberately excluded: that is an answer, not an outage. - Probe readiness rather than connectedness. checkViperblockReady tested natsConn.IsConnected(), which is true against a server whose JetStream is still starting; it now calls AccountInfo with a timeout. - Reconsider a latched failure. RetryRecoveryFailed relaunches instances parked in error by a dependency-unavailable reason, once the backing store is confirmed ready, for a bounded number of passes, then leaves them alone. Only mount-state and launch failures qualify; a failed reconnect or pre-relaunch hook can mean the guest itself needs looking at. Closes the e2e gap that made this feel covered. The node-failure tests stop the service units directly so guests keep running and recovery is a QMP re-attach to a live process. Nothing stopped a guest, so nothing relaunched one from a sealed volume against dependencies that were still starting. TestMultinodeClusterRestartResumesGuests does, including the wait for every spx process to exit that makes it a real restart rather than a stop that returned early. Beads mulga-v7kp4, mulga-kyb1d. * fix: stop every spx process by unit in the cluster-restart helper The first CI run of TestMultinodeClusterRestartResumesGuests failed at the process wait with "node1 still has 2 spx process(es)", and the same thing happened by hand on a prod node. Every service is the same binary, and the stop was driven from a hard-coded list. StopNode names seven units and omits spinifex-northstar and spinifex-qmp-collector, which is exactly the two survivors. The unit name now comes from the surviving process's cgroup, so the set that stops is whoever is still running the binary and the list cannot go stale when a service is added. StopNode itself is left alone: its list is deliberate for the hard outage it simulates, where the target's drain must not fire. Also marks recovery_retry_test.go as //test:in-package — the retry policy is unexported and the tests drive it through the seams restore_test.go uses. * fix: stop spinifex.target in the cluster-restart helper, not the units The second CI run reformed NATS to 2 peers of 3: node3's NATS was stopped and never started again. StopNode stops the service units directly and leaves spinifex.target active. That is right for the hard outage it simulates and wrong for a real shutdown — the target then reads as satisfied while its units are dead, and the later start raced a stop job that was still queued. The helper now runs what the operator procedure and update-nodes.sh run: stop spinifex.target, wait for every spx process to exit, and additionally wait for systemd to have no spinifex job left queued before starting. That last wait is the one that closes this race. * fix: wait for the right NATS peer count in the cluster-restart helper WaitNATSPeers counts remote peers, so a healthy three-node cluster reports two. Asking for three could never be satisfied, and the run that hit it had in fact restarted all three NATS correctly.
121 lines
4.4 KiB
Go
121 lines
4.4 KiB
Go
//go:build e2e
|
|
|
|
package multinode
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// Top-level Test* wrappers, each delegating to a runX function. Parallel tests
|
|
// share the read-only singleton trio; sequential tests mutate cluster state
|
|
// (stop/start nodes, predastore, EIP pool) and are ordered so the cluster is
|
|
// fully restabilised before later tests probe the trio.
|
|
//
|
|
// Pre-flight (/dev/kvm writability, peer SSH reachability) runs inside
|
|
// requireMultiNodeFixture itself, so the package fixture singleton fails
|
|
// fast with a clear message before any Test* body executes.
|
|
|
|
// Baseline test owns a dedicated SG and self-cleaning instances; it runs
|
|
// before needInstanceTrio so the default SG/subnet/route table are in
|
|
// pristine state.
|
|
func TestMultinodeSameSGCrossHostComms(t *testing.T) {
|
|
runMultinodeSameSGCrossHostComms(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
func TestMultinodeClusterHealth(t *testing.T) {
|
|
t.Parallel()
|
|
runClusterHealth(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeDNS is sequential because it launches guests and briefly stops
|
|
// one Northstar unit while exercising resolver failover.
|
|
func TestMultinodeDNS(t *testing.T) {
|
|
runMultinodeDNS(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeJetStreamReplicas is read-only over NATS; parallel so it resumes
|
|
// after the sequential node-failure/recovery tests have restabilised the cluster.
|
|
func TestMultinodeJetStreamReplicas(t *testing.T) {
|
|
t.Parallel()
|
|
runJetStreamReplicas(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeVolumeDurability is sequential and declared before CrossNodeOps
|
|
// (which destabilises trio[0]'s sshd) so its guest-SSH probes hit settled VMs.
|
|
// Touches predastore state.
|
|
func TestMultinodeVolumeDurability(t *testing.T) {
|
|
runVolumeDurability(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeCrossNodeGateway is sequential: asserts a stable per-gateway instance count
|
|
// that concurrent launches/terminates would break.
|
|
func TestMultinodeCrossNodeGateway(t *testing.T) {
|
|
runCrossNodeGateway(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeInstanceStatusFanout is sequential and declared before
|
|
// CrossNodeOps (which stops trio[0]) so the trio is still spread across nodes
|
|
// when the fan-out spread is measured.
|
|
func TestMultinodeInstanceStatusFanout(t *testing.T) {
|
|
runInstanceStatusFanout(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeCrossNodeOps is sequential: stops/starts trio[0].
|
|
func TestMultinodeCrossNodeOps(t *testing.T) {
|
|
runCrossNodeOps(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
func TestMultinodeNodeFailure(t *testing.T) {
|
|
runNodeFailure(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
func TestMultinodeNodeRecovery(t *testing.T) {
|
|
runNodeRecovery(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeClusterRestartResumesGuests is sequential and declared after
|
|
// NodeRecovery so the cluster is whole before it takes the whole thing down.
|
|
// It is the software-update path — a coordinated drain and restart — which the
|
|
// node-failure tests deliberately do not cover: they leave guests running, and
|
|
// the failure this catches only appears when a guest has to relaunch from a
|
|
// sealed volume against dependencies that are still starting.
|
|
func TestMultinodeClusterRestartResumesGuests(t *testing.T) {
|
|
runClusterRestartResumesGuests(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeOVNRaft is sequential: stops ovn-central on the NB leader to
|
|
// prove DB failover, restoring it in cleanup before later tests run.
|
|
func TestMultinodeOVNRaft(t *testing.T) {
|
|
runOVNRaft(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeFirewallChassisQuorum is sequential: it edits one node's config
|
|
// and restarts that node's daemon, restoring both before it returns.
|
|
func TestMultinodeFirewallChassisQuorum(t *testing.T) {
|
|
runFirewallChassisQuorum(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeSpread is sequential after NodeRecovery; owns 10.100.0.0/16 + EIP pool.
|
|
func TestMultinodeSpread(t *testing.T) {
|
|
runSpread(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeVPCNetworking owns 10.200.0.0/16 (no EIP use); safe to run in parallel.
|
|
func TestMultinodeVPCNetworking(t *testing.T) {
|
|
t.Parallel()
|
|
runVPCNetworking(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeIPSec is read-only over SSH; launches no AWS resources.
|
|
func TestMultinodeIPSec(t *testing.T) {
|
|
t.Parallel()
|
|
runIPSec(t, requireMultiNodeFixture(t))
|
|
}
|
|
|
|
// TestMultinodeListenerInvariant is read-only over SSH; launches no AWS
|
|
// resources and mutates no cluster state.
|
|
func TestMultinodeListenerInvariant(t *testing.T) {
|
|
t.Parallel()
|
|
runListenerInvariant(t, requireMultiNodeFixture(t))
|
|
}
|