fix: skip the guest-port datapath probe when the LSP is absent

applyPorts refuses a port whose SG policy cannot be programmed, but applyEIPs
still ran ensureGuestPortDatapath for every public-IP guest it skipped. GuestPortUp
reports false, not an error, for a port that does not exist, so each refused port
burned the full 45s deadline and ~9 ovn-controller recomputes before giving up.
This commit is contained in:
Julian Sommer
2026-08-31 13:36:26 +10:00
parent 11e0939652
commit fd11077b3a
3 changed files with 57 additions and 9 deletions
+8
View File
@@ -762,6 +762,14 @@ func (r *reconciler) ensureGuestPortDatapath(ctx context.Context, spec policy.EI
"retry_in_ms", otelsetup.Millis(time.Until(until)))
return
}
// No LSP, nothing to bind: a recompute cannot conjure one, so probing would
// burn the whole deadline. applyPorts refuses a port whose SG policy is
// unprogrammable, which lands here for every public-IP guest it skipped.
if _, err := r.ovn.GetLogicalSwitchPort(ctx, lspName); err != nil {
slog.Warn("reconcile/apply: guest LSP absent; skipping datapath probe",
"vpc_id", vpcID, "lsp", lspName, "eni_id", eniIDFromPort(lspName), "err", err)
return
}
deadline := time.Now().Add(guestPortDatapathTimeout)
nudged := false
misses := 0
@@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/mulgadc/spinifex/spinifex/network/ovn/mock"
"github.com/mulgadc/spinifex/spinifex/network/ovn/nbdb"
"github.com/mulgadc/spinifex/spinifex/network/policy"
)
@@ -130,6 +131,23 @@ func withFastGuestPortBounds(t *testing.T) {
t.Cleanup(func() { guestPortDatapathTimeout, guestPortDatapathInterval = to, iv })
}
// ovnWithGuestLSP returns a mock OVN client carrying lspName, the precondition
// ensureGuestPortDatapath now checks before it starts nudging.
func ovnWithGuestLSP(t *testing.T, lspName string) *mock.Client {
t.Helper()
m := mock.New()
if err := m.Connect(context.Background()); err != nil {
t.Fatalf("mock Connect: %v", err)
}
if err := m.CreateLogicalSwitch(context.Background(), &nbdb.LogicalSwitch{Name: "ls-test"}); err != nil {
t.Fatalf("CreateLogicalSwitch: %v", err)
}
if err := m.CreateLogicalSwitchPort(context.Background(), "ls-test", &nbdb.LogicalSwitchPort{Name: lspName}); err != nil {
t.Fatalf("CreateLogicalSwitchPort: %v", err)
}
return m
}
func withFastDatapathBounds(t *testing.T) {
t.Helper()
to, iv := gatewayDatapathTimeout, gatewayDatapathInterval
@@ -402,10 +420,32 @@ func TestEnsureGuestPortDatapath_EmptyLSPIsNoop(t *testing.T) {
}
}
// applyPorts refuses a port whose SG policy is unprogrammable, so applyEIPs then
// runs for a public-IP guest with no LSP. A recompute cannot bind a port that was
// never created, so probing it would burn the whole deadline for nothing.
func TestEnsureGuestPortDatapath_AbsentLSPSkipsProbe(t *testing.T) {
withFastGuestPortBounds(t)
f := &fakeClaimVerifier{guestUpAfter: -1}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-other")}
start := time.Now()
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
if elapsed := time.Since(start); elapsed >= guestPortDatapathTimeout {
t.Errorf("elapsed = %s, want well under the %s deadline", elapsed, guestPortDatapathTimeout)
}
if f.guestChecks != 0 {
t.Errorf("guestChecks = %d, want 0 (no LSP to probe)", f.guestChecks)
}
if f.nudges != 0 {
t.Errorf("nudges = %d, want 0 (a recompute cannot create an LSP)", f.nudges)
}
}
func TestEnsureGuestPortDatapath_UpNoNudge(t *testing.T) {
withFastGuestPortBounds(t)
f := &fakeClaimVerifier{guestUpAfter: 0}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
@@ -424,7 +464,7 @@ func TestEnsureGuestPortDatapath_NudgeThenConverge(t *testing.T) {
withFastGuestPortBounds(t)
// Down until one recompute nudge, then up.
f := &fakeClaimVerifier{guestUpAfter: 1}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
@@ -436,7 +476,7 @@ func TestEnsureGuestPortDatapath_NudgeThenConverge(t *testing.T) {
func TestEnsureGuestPortDatapath_NeverConvergesRecomputesEachMiss(t *testing.T) {
withFastGuestPortBounds(t)
f := &fakeClaimVerifier{guestUpAfter: -1} // never up
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
done := make(chan struct{})
go func() {
@@ -462,7 +502,7 @@ func TestEnsureGuestPortDatapath_NeverConvergesRecomputesEachMiss(t *testing.T)
func TestEnsureGuestPortDatapath_ProbeErrorBailsOut(t *testing.T) {
withFastGuestPortBounds(t)
f := &fakeClaimVerifier{guestErr: errors.New("ovn-sbctl down")}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
@@ -478,7 +518,7 @@ func TestEnsureGuestPortDatapath_ContextCancelStops(t *testing.T) {
t.Cleanup(func() { guestPortDatapathTimeout, guestPortDatapathInterval = to, iv })
f := &fakeClaimVerifier{guestUpAfter: -1}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
@@ -550,7 +590,7 @@ func TestEnsureGuestPortDatapath_WedgedSBEscalatesResetOnce(t *testing.T) {
withFastGuestPortBounds(t)
withSmallSBResetThreshold(t)
f := &fakeClaimVerifier{guestUpAfter: -1, sbNotConnected: true}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
runToDeadline(t, func() {
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
@@ -565,7 +605,7 @@ func TestEnsureGuestPortDatapath_ConnectedSBNeverResets(t *testing.T) {
withFastGuestPortBounds(t)
withSmallSBResetThreshold(t)
f := &fakeClaimVerifier{guestUpAfter: -1, sbNotConnected: false}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
runToDeadline(t, func() {
r.ensureGuestPortDatapath(context.Background(), policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"})
@@ -29,7 +29,7 @@ func TestEnsureGuestPortDatapath_BacksOffAfterFailure(t *testing.T) {
withFastGuestPortBackoff(t, time.Minute, time.Hour)
f := &fakeClaimVerifier{guestUpAfter: -1} // never up
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
spec := policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"}
r.ensureGuestPortDatapath(context.Background(), spec)
@@ -72,7 +72,7 @@ func TestEnsureGuestPortDatapath_BackoffClearsOnConverge(t *testing.T) {
withFastGuestPortBackoff(t, time.Millisecond, time.Millisecond)
f := &fakeClaimVerifier{guestUpAfter: -1}
r := &reconciler{gwClaim: f}
r := &reconciler{gwClaim: f, ovn: ovnWithGuestLSP(t, "port-eni-1")}
spec := policy.EIPSpec{VPCID: "vpc-a", PortName: "port-eni-1"}
r.ensureGuestPortDatapath(context.Background(), spec)