mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 19:56:33 +00:00
62e7f983ee
* test: assert the gateway->vpcd->OVN crossing in the integration tier
The tier already ran the real VPCServiceImpl for the AWS-facing half, but
nothing consumed the vpc.* events it published: the SG topics were answered
by a canned {"success":true} and the fire-and-forget topology topics were
dropped. Both sides of that wire were tested only against stubs, so the join
was asserted nowhere below the live e2e tier.
Wire a real subscribers.Subscriber over a real in-process OVN Northbound DB
(ovntest.StartNB), opt-in per test so only tests that assert OVN state pay
the startup cost. StartDaemonLite gains WithRealVPCD to suppress its stubs,
since a stub and the subscriber both answering one request-reply would race.
Scope is the crossing, not NB row shapes — those stay with the reconcile
scenario tests. Everything host-dependent (ovn-controller, OVS, taps, netns,
nftables, packet paths) remains the live tier's claim.
* fix: make the integration tier re-runnable and quiet the ensure probe
Two defects the vpcd/OVN work surfaced.
The tier was green at -count=1 and red package-wide at -count=2. Test
accounts were keyed by t.Name(), which is unique among tests but repeats
across passes in one TestMain, so a second pass inherited the first pass's
JetStream namespace — including an ECR signing key sealed with a master key
StartGateway regenerates per call. Key each account off a counter instead.
TestRunInstances_ClientTokenIdempotency then collided with itself for the
same reason: its dedup store outlives a pass, so its fixed ClientToken made
the second pass's opening launch look like a replay. Suffix the tokens per
invocation.
ensureNamedRowOps' wait-op is built to fail fast when the row it guards
already exists, which is the ordinary idempotent path, but transactOps
reported it as "OVSDB operation failed" at ERROR. Every re-ensure emitted
one, drowning real faults in the log sink. Classify that one result as the
probe outcome it is and log it at Debug.
* test: cover transactOps' failed-result paths
The classifier split one log line into two branches and neither was
exercised: transactOps sat at 31% because EnsureLogicalRouter's cache
lookup absorbs a re-ensure before it ever transacts, so no unit test
reached a failed operation result.
Drive both kinds directly against a real NB — the guard probe finding an
existing row, and two same-named inserts violating the schema's name
index — since only the second is a fault and they now log at different
levels. transactOps goes to 94%; the remainder is the transport-error
return, which needs a broken connection to reach.
118 lines
3.9 KiB
Go
118 lines
3.9 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/mulgadc/spinifex/spinifex/network/topology"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// startVPCDStack wires the full API-to-OVN chain: the real gateway router, a
|
|
// real vpcd event consumer over a real OVN NB DB, and the real VPC service
|
|
// impl answering the ec2.* subjects.
|
|
//
|
|
// Order matters. StartVPCDLite runs first because StartDaemonLite's
|
|
// EnsureDefaultVPC synchronously requests vpc.create-sg, which now has a real
|
|
// responder rather than a canned ack.
|
|
func startVPCDStack(t *testing.T) (*VPCDLite, *ec2.EC2) {
|
|
t.Helper()
|
|
|
|
gw := StartGateway(t)
|
|
vpcd := StartVPCDLite(t, gw)
|
|
StartDaemonLite(t, gw, WithRealVPCD())
|
|
|
|
return vpcd, gw.EC2Client(t)
|
|
}
|
|
|
|
// TestVPCD_VPCLifecycleReachesOVN asserts CreateVpc/DeleteVpc travel the whole
|
|
// chain — SigV4 request, gateway, ec2.CreateVpc, VPCServiceImpl, vpc.create,
|
|
// subscriber, topology manager, OVSDB transaction — and land as a logical
|
|
// router carrying the VPC's identity.
|
|
//
|
|
// Row contents beyond identity are network/reconcile's scenario tests to
|
|
// assert; what is unique here is that the wire between the two services
|
|
// carries the event at all.
|
|
func TestVPCD_VPCLifecycleReachesOVN(t *testing.T) {
|
|
vpcd, c := startVPCDStack(t)
|
|
|
|
vpcID := createScratchVPC(t, c, "10.42.0.0/16")
|
|
router := topology.VPCRouter(vpcID)
|
|
|
|
awaitNB(t, "logical router "+router, func(ctx context.Context) error {
|
|
lr, err := vpcd.OVN.GetLogicalRouter(ctx, router)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if got := lr.ExternalIDs["spinifex:vpc_id"]; got != vpcID {
|
|
return fmt.Errorf("spinifex:vpc_id = %q, want %q", got, vpcID)
|
|
}
|
|
if got := lr.ExternalIDs["spinifex:cidr"]; got != "10.42.0.0/16" {
|
|
return fmt.Errorf("spinifex:cidr = %q, want 10.42.0.0/16", got)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
_, err := c.DeleteVpc(&ec2.DeleteVpcInput{VpcId: aws.String(vpcID)})
|
|
require.NoError(t, err, "delete-vpc %s", vpcID)
|
|
|
|
awaitNB(t, "logical router "+router+" removed", func(ctx context.Context) error {
|
|
if _, err := vpcd.OVN.GetLogicalRouter(ctx, router); err == nil {
|
|
return fmt.Errorf("logical router %s still present", router)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// TestVPCD_SubnetLifecycleReachesOVN asserts CreateSubnet/DeleteSubnet reach
|
|
// OVN as a logical switch attached to the owning VPC's router.
|
|
func TestVPCD_SubnetLifecycleReachesOVN(t *testing.T) {
|
|
vpcd, c := startVPCDStack(t)
|
|
|
|
vpcID := createScratchVPC(t, c, "10.43.0.0/16")
|
|
out, err := c.CreateSubnet(&ec2.CreateSubnetInput{
|
|
VpcId: aws.String(vpcID),
|
|
CidrBlock: aws.String("10.43.1.0/24"),
|
|
})
|
|
require.NoError(t, err, "create-subnet in %s", vpcID)
|
|
require.NotNil(t, out.Subnet, "create-subnet returned nil Subnet")
|
|
subnetID := aws.StringValue(out.Subnet.SubnetId)
|
|
require.NotEmpty(t, subnetID, "SubnetId empty")
|
|
|
|
sw := topology.SubnetSwitch(subnetID)
|
|
awaitNB(t, "logical switch "+sw, func(ctx context.Context) error {
|
|
ls, err := vpcd.OVN.GetLogicalSwitch(ctx, sw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if got := ls.ExternalIDs["spinifex:subnet_id"]; got != subnetID {
|
|
return fmt.Errorf("spinifex:subnet_id = %q, want %q", got, subnetID)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// The subnet's default gateway terminates on an LRP of the VPC router, so
|
|
// its presence is what proves the switch was joined to the right VPC
|
|
// rather than merely created.
|
|
lrp := topology.SubnetRouterPort(subnetID)
|
|
awaitNB(t, "logical router port "+lrp, func(ctx context.Context) error {
|
|
_, err := vpcd.OVN.GetLogicalRouterPort(ctx, lrp)
|
|
return err
|
|
})
|
|
|
|
_, err = c.DeleteSubnet(&ec2.DeleteSubnetInput{SubnetId: aws.String(subnetID)})
|
|
require.NoError(t, err, "delete-subnet %s", subnetID)
|
|
|
|
awaitNB(t, "logical switch "+sw+" removed", func(ctx context.Context) error {
|
|
if _, err := vpcd.OVN.GetLogicalSwitch(ctx, sw); err == nil {
|
|
return fmt.Errorf("logical switch %s still present", sw)
|
|
}
|
|
return nil
|
|
})
|
|
}
|