Files
spinifex/tests/e2e/single/tests_test.go
T
joshsiv-mulga c9a4e904a8 test(e2e): fresh-install reachability baselines + drop qemu hostfwd fallback
Add baseline tests that confirm two fresh-install regressions the existing
suite masks (it authorizes tcp/22 on the default SG before every SSH probe):

  - default SG default-deny gates external reach of instances in the
    default subnet (SG, not routing)
  - a newly-created public subnet never gets its OVN IGW egress LRP, so it
    has no route to the VPC IGW despite MapPublicIpOnLaunch

Plus a positive test: instances sharing the default SG can communicate via
ICMP (same-host single-node, cross-host multinode).

Single-node baselines run after TestClusterStatsCLI (0-VM gate); multinode
after TestMultinodePreflight (fixture init). Instances are self-cleaning so
they don't perturb the singleton-VM ordering.

Remove the qemu hostfwd SSH fallback from the harness (InstancePublicSSHHost,
GuestSSHEndpoint) - it bypasses the OVN datapath and masks these regressions.
Endpoints now t.Fatal when no public IP is assigned.
2026-06-04 19:56:02 +10:00

260 lines
8.3 KiB
Go

//go:build e2e
package single
import "testing"
// Top-level Test* entry points. Each delegates to a runX function in the
// matching <name>_test.go file. Names are number-free (TestX) so isolated
// runs via `go test -run TestKeyPairs` are stable.
//
// Every run* function self-bootstraps prerequisites via harness.Discover* /
// harness.Ensure* (or the package-local need* / iamEnsure* helpers), so a
// targeted `go test -run TestX` invocation works in isolation without
// relying on a sibling Test* having stashed state.
//
// Parallelism (mulga-siv-128):
//
// Bucket #1 (parallel): read-only or own-everything Tests below call
// t.Parallel(). They share the package fixture but never mutate the
// singleton EC2 instance, default VPC, or default SG.
//
// Sequential (no t.Parallel — would race the singleton VM or shared state):
// - TestClusterStatsCLI : asserts baseline cluster state
// (0/ CPU, "No VMs found"); must run before any launches.
// - TestInstanceLaunch : first sequential — boots singleton.
// - TestInstanceClusterStats / Metadata
// / SSHProbe / ConsoleOutput : read singleton.
// - TestVolumeLifecycle / VolumeStatus : attach to singleton.
// - TestSnapshotLifecycle /
// SnapshotBackedLaunch / CreateImage : snapshot singleton.
// - TestSecurityGroupEgress : mutates default SG.
// - TestStopStart / AttachToStoppedError
// / ModifyInstanceAttribute /
// RebootInstance / RunInstancesMultiCount : mutate singleton state.
// - TestNegativeErrorPaths : sub-tests already parallel inside.
// - TestNATGateway / TestSGToSGDatapath : share EIP pool.
// - All TestIAM* : share IAM policy state via the
// package-scoped IAM fixture; sub-tests inside each already parallel.
// - TestFinalClusterStats : final sanity gate.
// --- Bucket #1: parallel-safe ---
func TestEnvironment(t *testing.T) {
t.Parallel()
runEnvironment(t, requireSingleNodeFixture(t))
}
func TestDiscovery(t *testing.T) {
t.Parallel()
runDiscovery(t, requireSingleNodeFixture(t))
}
func TestSerialConsoleAccess(t *testing.T) {
t.Parallel()
runSerialConsoleAccess(t, requireSingleNodeFixture(t))
}
func TestKeyPairs(t *testing.T) {
t.Parallel()
runKeyPairs(t, requireSingleNodeFixture(t))
}
func TestImage(t *testing.T) {
t.Parallel()
runImage(t, requireSingleNodeFixture(t))
}
func TestTagManagement(t *testing.T) {
t.Parallel()
runTagManagement(t, requireSingleNodeFixture(t))
}
func TestAccountScoping(t *testing.T) {
t.Parallel()
runAccountScoping(t, requireSingleNodeFixture(t))
}
func TestVPCSubnetE2E(t *testing.T) {
t.Parallel()
runVPCSubnetE2E(t, requireSingleNodeFixture(t))
}
func TestRouteTableValidation(t *testing.T) {
t.Parallel()
runRouteTableValidation(t, requireSingleNodeFixture(t))
}
// --- Sequential: singleton VM lifecycle ---
// TestClusterStatsCLI runs before TestInstanceLaunch because it asserts
// baseline cluster state (0/ CPU used, "No VMs found") that doesn't hold
// once the singleton instance is up.
func TestClusterStatsCLI(t *testing.T) {
runClusterStatsCLI(t, requireSingleNodeFixture(t))
}
// Fresh-install reachability baselines run right after the 0-VM cluster-stats
// gate and before the singleton launch. They own all mutable resources
// (dedicated SG / subnet / self-cleaning instance) and never mutate the
// default SG, subnet, or route table.
func TestDefaultSGReachabilityBaseline(t *testing.T) {
runDefaultSGReachabilityBaseline(t, requireSingleNodeFixture(t))
}
func TestNewSubnetPublicEgressBaseline(t *testing.T) {
runNewSubnetPublicEgressBaseline(t, requireSingleNodeFixture(t))
}
func TestSameSGComms(t *testing.T) {
runSameSGComms(t, requireSingleNodeFixture(t))
}
func TestInstanceLaunch(t *testing.T) {
runInstanceLaunch(t, requireSingleNodeFixture(t))
}
func TestInstanceClusterStats(t *testing.T) {
runInstanceClusterStats(t, requireSingleNodeFixture(t))
}
func TestInstanceMetadata(t *testing.T) {
runInstanceMetadata(t, requireSingleNodeFixture(t))
}
func TestSSHProbe(t *testing.T) {
runSSHProbe(t, requireSingleNodeFixture(t))
}
func TestConsoleOutput(t *testing.T) {
runConsoleOutput(t, requireSingleNodeFixture(t))
}
func TestVolumeLifecycle(t *testing.T) {
runVolumeLifecycle(t, requireSingleNodeFixture(t))
}
func TestVolumeStatus(t *testing.T) {
runVolumeStatus(t, requireSingleNodeFixture(t))
}
func TestSnapshotLifecycle(t *testing.T) {
runSnapshotLifecycle(t, requireSingleNodeFixture(t))
}
func TestSnapshotBackedLaunch(t *testing.T) {
runSnapshotBackedLaunch(t, requireSingleNodeFixture(t))
}
func TestCreateImage(t *testing.T) {
runCreateImage(t, requireSingleNodeFixture(t))
}
func TestSecurityGroupEgress(t *testing.T) {
runSecurityGroupEgress(t, requireSingleNodeFixture(t))
}
func TestStopStart(t *testing.T) {
runStopStart(t, requireSingleNodeFixture(t))
}
func TestAttachToStoppedError(t *testing.T) {
runAttachToStoppedError(t, requireSingleNodeFixture(t))
}
func TestModifyInstanceAttribute(t *testing.T) {
runModifyInstanceAttribute(t, requireSingleNodeFixture(t))
}
func TestRebootInstance(t *testing.T) {
runRebootInstance(t, requireSingleNodeFixture(t))
}
func TestRunInstancesMultiCount(t *testing.T) {
runRunInstancesMultiCount(t, requireSingleNodeFixture(t))
}
// --- Sequential: shared-state / sub-test-parallel Tests ---
func TestNegativeErrorPaths(t *testing.T) {
runNegativeErrorPaths(t, requireSingleNodeFixture(t))
}
func TestNATGateway(t *testing.T) {
runNATGateway(t, requireSingleNodeFixture(t))
}
func TestSGToSGDatapath(t *testing.T) {
runSGToSGDatapath(t, requireSingleNodeFixture(t))
}
// IAM Tests share the package-scoped IAM fixture; sub-tests inside each
// runIAM* already use t.Parallel for fan-out.
func TestIAMUserCRUD(t *testing.T) {
runIAMUserCRUD(t, requireSingleNodeFixture(t))
}
func TestIAMAccessKeyLifecycle(t *testing.T) {
runIAMAccessKeyLifecycle(t, requireSingleNodeFixture(t))
}
func TestIAMUserAuthentication(t *testing.T) {
runIAMUserAuthentication(t, requireSingleNodeFixture(t))
}
func TestIAMPolicyCRUD(t *testing.T) {
runIAMPolicyCRUD(t, requireSingleNodeFixture(t))
}
func TestIAMPolicyAttachmentEnforcement(t *testing.T) {
runIAMPolicyAttachmentEnforcement(t, requireSingleNodeFixture(t))
}
func TestIAMPolicyLifecycle(t *testing.T) {
runIAMPolicyLifecycle(t, requireSingleNodeFixture(t))
}
func TestIAMCleanup(t *testing.T) {
runIAMCleanup(t, requireSingleNodeFixture(t))
}
// TestIAMRolesAndProfiles + TestIAMInstanceProfileAssociation form the
// companion-plan suite (docs/development/archive/feature/iam-roles-v1.md and
// docs/development/feature/iam-roles-v1-ec2.md). Sequential: the second test
// recreates the same role/profile names the first tore down, so concurrent
// runs would race the EntityAlreadyExists / NoSuchEntity boundaries.
func TestIAMRolesAndProfiles(t *testing.T) {
runIAMRolesAndProfiles(t, requireSingleNodeFixture(t))
}
// TestIAMInstanceProfileAssociation also briefly mutates the singleton VM
// (Associate/Disassociate), so it can't share the parallel bucket with
// instance-mutation tests.
func TestIAMInstanceProfileAssociation(t *testing.T) {
runIAMInstanceProfileAssociation(t, requireSingleNodeFixture(t))
}
// TestSTSAssumeRoleAndGetCallerIdentity exercises the STS v1 surface. Owns
// its own role, so safe alongside the IAM Roles tests above, but sequential
// so trust-policy mutations don't race a parallel AssumeRole.
func TestSTSAssumeRoleAndGetCallerIdentity(t *testing.T) {
runSTS(t, requireSingleNodeFixture(t))
}
// TestIMDS exercises the host-served IMDSv2 surface end-to-end: token
// issuance, the v2-only stance, the metadata surface, the
// instance-role credential path + wire round-trip, the per-subnet L2 localport
// datapath (ovn-trace round-trip), and cross-VPC source-IP isolation. Owns its
// own role/profile + a second VPC, but sequential: it launches profile-bound VMs
// and a fresh VPC, so it must not race the singleton-mutation tests above.
func TestIMDS(t *testing.T) {
runIMDS(t, requireSingleNodeFixture(t))
}
// TestFinalClusterStats runs as the last sequential test (parallel bucket
// runs afterwards, but those Tests are read-only / own-everything so a
// later cluster-stats snapshot would still be valid).
func TestFinalClusterStats(t *testing.T) {
runFinalClusterStats(t, requireSingleNodeFixture(t))
}