Files
spinifex/tests/integration/vpc_ordering_test.go
T
joshsiv-mulga 1050485944 refactor: use contracts/network/v1 for vpc.* producers and consumers
Switch every vpc.* producer (domains/ec2/vpc, eip, instance, natgw,
routetable, igw, handlers/elbv2, handlers/eks, daemon, systemvpc) and the
network/subscribers consumer onto contracts/network/v1 subjects, event
types and the shared AckEnvelope. contracts/network/v1 becomes the sole
owner of vpc.igw-attach/detach, replacing the duplicate definitions in
contracts/ec2/v1.

Deletes the duplicated local subject constants and event structs:
subscribers' Topic* consts and event structs, vpc.NATEvent and SGEvent,
eip's natEvent, natgw's natGatewayEvent, and eks's systemEgressEvent.
domains/ec2/vpc.SGRule survives because EC2 persists it directly in
SecurityGroupRecord; contracts/network/v1.SecurityGroupRule was expanded
in slice 2 to match it field-for-field so wire bytes are unchanged.

No lifecycle or reliability changes: the NAT add barrier, delete
retry-on-ErrNoResponders, and the inconsistent per-call-site NAT
delivery modes (EIP AssociateAddress and DeleteNetworkInterface both stay
fire-and-forget with no ack) are preserved exactly, as are the golden
wire literals pinned in the characterization tests.
2026-10-09 18:39:02 +10:00

99 lines
3.2 KiB
Go

//go:build integration
package integration
import (
"context"
"encoding/json"
"fmt"
"testing"
networkv1 "github.com/mulgadc/spinifex/contracts/network/v1"
"github.com/mulgadc/spinifex/spinifex/domains/network/topology"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/require"
)
// startVPCDEvents wires a real vpcd consumer over a real OVN NB DB with no
// daemon behind it. These tests publish the vpc.* events directly because the
// property under test is delivery order, which the SDK path cannot produce on
// demand: through it, CreateVpc necessarily precedes CreateSubnet.
func startVPCDEvents(t *testing.T) (*VPCDLite, *nats.Conn) {
t.Helper()
gw := StartGateway(t)
return StartVPCDLite(t, gw), gw.NATSConn
}
// publishEvent marshals and fires a fire-and-forget vpc.* topology event, the
// way domains/ec2/vpc's publishVPCEvent/publishSubnetEvent do.
func publishEvent(t *testing.T, nc *nats.Conn, topic string, evt any) {
t.Helper()
payload, err := json.Marshal(evt)
require.NoError(t, err, "marshal %s event", topic)
require.NoError(t, nc.Publish(topic, payload), "publish %s", topic)
require.NoError(t, nc.Flush(), "flush %s", topic)
}
// countRouters returns how many logical routers carry name.
func countRouters(ctx context.Context, vpcd *VPCDLite, name string) (int, error) {
routers, err := vpcd.OVN.ListLogicalRouters(ctx)
if err != nil {
return 0, err
}
n := 0
for _, r := range routers {
if r.Name == name {
n++
}
}
return n, nil
}
// TestVPCD_SubnetBeforeVPCConverges covers the case where vpc.create-subnet
// is delivered with no preceding vpc.create. The two topics have no ordering
// guarantee, so on new-tenant bootstrap the subnet can genuinely land first;
// the subnet handler's defensive pre-ensure is what stops that from being a
// dropped subnet.
//
// The convergence claim is the point: the pre-ensure creates the router
// without a CIDR, and the real vpc.create arriving afterwards must fill it in
// rather than being discarded as a duplicate or forking a second router.
func TestVPCD_SubnetBeforeVPCConverges(t *testing.T) {
vpcd, nc := startVPCDEvents(t)
ctx := context.Background()
const vpcID, subnetID = "vpc-order-a", "subnet-order-a"
router := topology.VPCRouter(vpcID)
publishEvent(t, nc, networkv1.SubnetCreateSubject, networkv1.SubnetEvent{
SubnetId: subnetID,
VpcId: vpcID,
CidrBlock: "10.50.1.0/24",
})
awaitNB(t, "logical switch for subnet-first delivery", func(ctx context.Context) error {
_, err := vpcd.OVN.GetLogicalSwitch(ctx, topology.SubnetSwitch(subnetID))
return err
})
publishEvent(t, nc, networkv1.VPCCreateSubject, networkv1.VPCEvent{
VpcId: vpcID,
CidrBlock: "10.50.0.0/16",
})
awaitNB(t, "VPC CIDR backfilled onto pre-ensured router", func(ctx context.Context) error {
lr, err := vpcd.OVN.GetLogicalRouter(ctx, router)
if err != nil {
return err
}
if got := lr.ExternalIDs["spinifex:cidr"]; got != "10.50.0.0/16" {
return fmt.Errorf("spinifex:cidr = %q, want 10.50.0.0/16", got)
}
return nil
})
n, err := countRouters(ctx, vpcd, router)
require.NoError(t, err, "list logical routers")
require.Equal(t, 1, n, "out-of-order delivery forked %s into %d routers", router, n)
}