Files
spinifex/tests/integration/vpc_sg_test.go
T
Josh Siviour 62e7f983ee test: assert the gateway->vpcd->OVN crossing in the integration tier (#572)
* 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.
2026-07-22 18:17:24 +10:00

67 lines
2.5 KiB
Go

//go:build integration
package integration
import (
"context"
"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"
)
// TestVPCD_SecurityGroupReachesOVN asserts the synchronous half of the
// handler-to-vpcd contract against a real responder. The SG topics use
// utils.RequestEvent, so unlike the topology events these need no polling: by
// the time the SDK call returns, vpcd has already committed to OVN.
//
// This is the case the tier previously faked outright with a canned
// {"success":true} — the API call succeeded whether or not any OVN work was
// possible, let alone correct.
func TestVPCD_SecurityGroupReachesOVN(t *testing.T) {
vpcd, c := startVPCDStack(t)
ctx := context.Background()
vpcID := createScratchVPC(t, c, "10.44.0.0/16")
sgOut, err := c.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
VpcId: aws.String(vpcID),
GroupName: aws.String("integration-sg"),
Description: aws.String("vpcd integration tier"),
})
require.NoError(t, err, "create-security-group in %s", vpcID)
sgID := aws.StringValue(sgOut.GroupId)
require.NotEmpty(t, sgID, "GroupId empty")
pgName := topology.SecurityGroupPortGroup(sgID)
pg, err := vpcd.OVN.GetPortGroup(ctx, pgName)
require.NoError(t, err, "SG port group %s absent after create-security-group", pgName)
baseACLs := len(pg.ACLs)
_, err = c.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
GroupId: aws.String(sgID),
IpPermissions: []*ec2.IpPermission{{
IpProtocol: aws.String("tcp"),
FromPort: aws.Int64(22),
ToPort: aws.Int64(22),
IpRanges: []*ec2.IpRange{{CidrIp: aws.String("10.44.0.0/16")}},
}},
})
require.NoError(t, err, "authorize-security-group-ingress on %s", sgID)
// The rule count is the assertion, not the ACL match text: that the rule
// crossed the wire at all is this tier's claim, while the match expression
// it compiles to belongs to policy's own tests.
pg, err = vpcd.OVN.GetPortGroup(ctx, pgName)
require.NoError(t, err, "SG port group %s after authorize", pgName)
require.Greater(t, len(pg.ACLs), baseACLs,
"authorize-security-group-ingress added no ACL to port group %s", pgName)
_, err = c.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{GroupId: aws.String(sgID)})
require.NoError(t, err, "delete-security-group %s", sgID)
_, err = vpcd.OVN.GetPortGroup(ctx, pgName)
require.Error(t, err, "SG port group %s still present after delete-security-group", pgName)
}