integration: reproduce HA primary flap to offline node via docker disconnect

Add a docker-network-disconnect primitive — `DisconnectFromNetwork`
/ `ReconnectToNetwork` on `TailscaleClient`, backed by
`pool.Client.DisconnectNetwork` — so an integration test can
faithfully simulate the cable-pull scenario the issue reporter
observed in Proxmox. Iptables-based simulations leave the socket
open at the container kernel and miss the bug.

Use it in `TestHASubnetRouterFailoverDockerDisconnect` to assert
the no-flap invariant: once primary has failed over from r1 to r2
and r2 also loses connectivity, primary must not switch back to
the still-offline r1. The assertion fails on the current branch —
the all-unhealthy fallback in `electPrimaryRoutes` flips primary
to the lowest-NodeID candidate regardless of online state. Locking
the failure in via TDD before designing the fix.

Updates #3203
This commit is contained in:
Kristoffer Dalby committed 2026-04-29 12:25:40 +00:00
1 parent 2d04fff605
commit 5228818a12
4 files changed
+250

No files matched your search

+15
View File
@@ -807,6 +807,21 @@ func (t *TailscaleInContainer) Down() error {
return nil
}
// DisconnectFromNetwork detaches the container from network at the
// docker daemon level. The container's network interface for that
// network disappears and any in-flight TCP connection is left
// half-open at the peer — the same failure mode a real cable pull
// produces, which iptables-based simulations cannot reproduce.
func (t *TailscaleInContainer) DisconnectFromNetwork(network *dockertest.Network) error {
return dockertestutil.DisconnectContainerFromNetwork(t.pool, network, t.hostname)
}
// ReconnectToNetwork is the inverse of DisconnectFromNetwork: it
// re-attaches the container to network so traffic can flow again.
func (t *TailscaleInContainer) ReconnectToNetwork(network *dockertest.Network) error {
return dockertestutil.ReconnectContainerToNetwork(t.pool, network, t.hostname)
}
// IPs returns the netip.Addr of the Tailscale instance.
func (t *TailscaleInContainer) IPs() ([]netip.Addr, error) {
if len(t.ips) != 0 {