mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-24 23:36:49 +09:00
servertest: retry via compat netmap comparison until converged
Peer count arrives before routes; compare-once raced route delivery.
This commit is contained in:
1 parent
3e00a42bd2
commit
74121aa339
2 files changed
+45
-18
No files matched your search
@@ -240,6 +240,9 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
saasAddrs := saasAddrsByNode(c, clients)
|
saasAddrs := saasAddrsByNode(c, clients)
|
||||||
|
|
||||||
// Compare each viewer's [tailcfg.MapResponse] against the golden [netmap.NetworkMap].
|
// Compare each viewer's [tailcfg.MapResponse] against the golden [netmap.NetworkMap].
|
||||||
|
// Route approvals reach clients asynchronously, so the peer-count wait
|
||||||
|
// above says nothing about routes, primaries or filters: retry the
|
||||||
|
// whole comparison until the viewer's netmap has caught up.
|
||||||
for viewerName, cl := range clients {
|
for viewerName, cl := range clients {
|
||||||
capture := c.Captures[viewerName]
|
capture := c.Captures[viewerName]
|
||||||
if capture.Netmap == nil {
|
if capture.Netmap == nil {
|
||||||
@@ -247,10 +250,12 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run(viewerName, func(t *testing.T) {
|
t.Run(viewerName, func(t *testing.T) {
|
||||||
nm := cl.Netmap()
|
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||||
require.NotNil(t, nm, "netmap is nil")
|
nm := cl.Netmap()
|
||||||
|
require.NotNil(collect, nm, "netmap is nil")
|
||||||
|
|
||||||
compareNetmap(t, nm, capture, clients, saasAddrs)
|
compareNetmap(collect, nm, capture, clients, saasAddrs)
|
||||||
|
}, 30*time.Second, 100*time.Millisecond)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,14 +267,18 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
// - Number of Tailscale IPs per peer (should be 2: one v4 + one v6)
|
// - Number of Tailscale IPs per peer (should be 2: one v4 + one v6)
|
||||||
// - PrimaryRoutes per peer
|
// - PrimaryRoutes per peer
|
||||||
// - PacketFilter (source, destination, ports) triples
|
// - PacketFilter (source, destination, ports) triples
|
||||||
|
//
|
||||||
|
// It takes [require.TestingT] so it can run inside [require.EventuallyWithT].
|
||||||
func compareNetmap(
|
func compareNetmap(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
got *netmap.NetworkMap,
|
got *netmap.NetworkMap,
|
||||||
want testcapture.Node,
|
want testcapture.Node,
|
||||||
clients map[string]*servertest.TestClient,
|
clients map[string]*servertest.TestClient,
|
||||||
saasAddrs map[netip.Addr]string,
|
saasAddrs map[netip.Addr]string,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
require.NotNil(t, want.Netmap, "golden Netmap is nil")
|
require.NotNil(t, want.Netmap, "golden Netmap is nil")
|
||||||
|
|
||||||
@@ -409,13 +418,15 @@ func compareNetmap(
|
|||||||
// Tailscale IPs differ between SaaS and headscale allocation, so both
|
// Tailscale IPs differ between SaaS and headscale allocation, so both
|
||||||
// sides are re-keyed by peer identity.
|
// sides are re-keyed by peer identity.
|
||||||
func comparePacketFilter(
|
func comparePacketFilter(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
got *netmap.NetworkMap,
|
got *netmap.NetworkMap,
|
||||||
want testcapture.Node,
|
want testcapture.Node,
|
||||||
clients map[string]*servertest.TestClient,
|
clients map[string]*servertest.TestClient,
|
||||||
saasAddrs map[netip.Addr]string,
|
saasAddrs map[netip.Addr]string,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
hsAddrs := hsAddrsByPeer(clients)
|
hsAddrs := hsAddrsByPeer(clients)
|
||||||
|
|
||||||
@@ -483,12 +494,14 @@ func capTriple(srcs, dst string, c peercap.Cap) string {
|
|||||||
// peerIdents resolves a prefix into sorted canonical identity tokens,
|
// peerIdents resolves a prefix into sorted canonical identity tokens,
|
||||||
// see [addIdentsForSrc].
|
// see [addIdentsForSrc].
|
||||||
func peerIdents(
|
func peerIdents(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
pfx netip.Prefix,
|
pfx netip.Prefix,
|
||||||
addrToPeer map[netip.Addr]string,
|
addrToPeer map[netip.Addr]string,
|
||||||
ruleIndex int,
|
ruleIndex int,
|
||||||
) []string {
|
) []string {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
addIdentsForSrc(t, pfx, addrToPeer, ruleIndex, seen)
|
addIdentsForSrc(t, pfx, addrToPeer, ruleIndex, seen)
|
||||||
@@ -558,12 +571,14 @@ func hsAddrsByPeer(clients map[string]*servertest.TestClient) map[netip.Addr]str
|
|||||||
// or /128 (IPv6) expands to the union of its contained peers.
|
// or /128 (IPv6) expands to the union of its contained peers.
|
||||||
// Unresolvable Tailscale-range sources fail the test.
|
// Unresolvable Tailscale-range sources fail the test.
|
||||||
func canonicaliseSrcStrings(
|
func canonicaliseSrcStrings(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
srcs []string,
|
srcs []string,
|
||||||
addrToPeer map[netip.Addr]string,
|
addrToPeer map[netip.Addr]string,
|
||||||
ruleIndex int,
|
ruleIndex int,
|
||||||
) []string {
|
) []string {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
|
|
||||||
@@ -588,12 +603,14 @@ func canonicaliseSrcStrings(
|
|||||||
// [canonicaliseSrcStrings], reading already-parsed [netip.Prefix] values
|
// [canonicaliseSrcStrings], reading already-parsed [netip.Prefix] values
|
||||||
// from [tailcfg.Match.Srcs].
|
// from [tailcfg.Match.Srcs].
|
||||||
func canonicaliseSrcPrefixes(
|
func canonicaliseSrcPrefixes(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
srcs []netip.Prefix,
|
srcs []netip.Prefix,
|
||||||
addrToPeer map[netip.Addr]string,
|
addrToPeer map[netip.Addr]string,
|
||||||
ruleIndex int,
|
ruleIndex int,
|
||||||
) []string {
|
) []string {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
|
|
||||||
@@ -615,13 +632,15 @@ func canonicaliseSrcPrefixes(
|
|||||||
// through literally; a Tailscale-range prefix expands to the union
|
// through literally; a Tailscale-range prefix expands to the union
|
||||||
// of peer names whose addresses fall within it.
|
// of peer names whose addresses fall within it.
|
||||||
func addIdentsForSrc(
|
func addIdentsForSrc(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
pfx netip.Prefix,
|
pfx netip.Prefix,
|
||||||
addrToPeer map[netip.Addr]string,
|
addrToPeer map[netip.Addr]string,
|
||||||
ruleIndex int,
|
ruleIndex int,
|
||||||
seen map[string]struct{},
|
seen map[string]struct{},
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
if !prefixInTailscaleRange(pfx) {
|
if !prefixInTailscaleRange(pfx) {
|
||||||
seen[pfx.String()] = struct{}{}
|
seen[pfx.String()] = struct{}{}
|
||||||
|
|||||||
@@ -186,6 +186,9 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compare each viewer's [tailcfg.MapResponse] against golden [netmap.NetworkMap].
|
// Compare each viewer's [tailcfg.MapResponse] against golden [netmap.NetworkMap].
|
||||||
|
// Route approvals reach clients asynchronously, so the peer-count wait
|
||||||
|
// above says nothing about routes or primaries: retry the whole
|
||||||
|
// comparison until the viewer's netmap has caught up.
|
||||||
for viewerName, cl := range clients {
|
for viewerName, cl := range clients {
|
||||||
capture := c.Captures[viewerName]
|
capture := c.Captures[viewerName]
|
||||||
if capture.Netmap == nil {
|
if capture.Netmap == nil {
|
||||||
@@ -193,7 +196,9 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run(viewerName, func(t *testing.T) {
|
t.Run(viewerName, func(t *testing.T) {
|
||||||
compareCaptureNetmap(t, cl, capture, clients)
|
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||||
|
compareCaptureNetmap(collect, cl, capture, clients)
|
||||||
|
}, 30*time.Second, 100*time.Millisecond)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,13 +206,16 @@ func runViaHACompat(t *testing.T, c *testcapture.Capture) {
|
|||||||
// compareCaptureNetmap compares headscale's [tailcfg.MapResponse] against a
|
// compareCaptureNetmap compares headscale's [tailcfg.MapResponse] against a
|
||||||
// [testcapture.Node]'s [netmap.NetworkMap] data. Same logic as [compareNetmap] but
|
// [testcapture.Node]'s [netmap.NetworkMap] data. Same logic as [compareNetmap] but
|
||||||
// reads from typed [testcapture] fields instead of goldenFile strings.
|
// reads from typed [testcapture] fields instead of goldenFile strings.
|
||||||
|
// It takes [require.TestingT] so it can run inside [require.EventuallyWithT].
|
||||||
func compareCaptureNetmap(
|
func compareCaptureNetmap(
|
||||||
t *testing.T,
|
t require.TestingT,
|
||||||
viewer *servertest.TestClient,
|
viewer *servertest.TestClient,
|
||||||
want testcapture.Node,
|
want testcapture.Node,
|
||||||
clients map[string]*servertest.TestClient,
|
clients map[string]*servertest.TestClient,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
if h, ok := t.(interface{ Helper() }); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
nm := viewer.Netmap()
|
nm := viewer.Netmap()
|
||||||
require.NotNil(t, nm, "viewer has no netmap")
|
require.NotNil(t, nm, "viewer has no netmap")
|
||||||
|
|||||||
Reference in new issue
Block a user