mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-27 08:46:49 +09:00
servertest: bound fixture batching and await observable state
Updates #3544
This commit is contained in:
1 parent
b16b705c13
commit
940260cfdc
6 files changed
+114
-51
No files matched your search
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/juanfont/headscale/hscontrol/servertest"
|
"github.com/juanfont/headscale/hscontrol/servertest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"tailscale.com/types/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestConsistency verifies that all nodes converge to the same
|
// TestConsistency verifies that all nodes converge to the same
|
||||||
@@ -86,14 +87,27 @@ func TestConsistency(t *testing.T) {
|
|||||||
c6 := h.AddClient(t)
|
c6 := h.AddClient(t)
|
||||||
c7 := h.AddClient(t)
|
c7 := h.AddClient(t)
|
||||||
|
|
||||||
// Wait for new nodes to see at least all other connected
|
// Disconnected peers can satisfy a count before the new peers arrive.
|
||||||
// clients (they may also see the disconnected nodes during
|
// Wait for each connected identity before comparing the mesh.
|
||||||
// the grace period, so we check >= not ==).
|
|
||||||
connected := h.ConnectedClients()
|
connected := h.ConnectedClients()
|
||||||
minPeers := len(connected) - 1
|
|
||||||
|
|
||||||
for _, c := range connected {
|
for _, c := range connected {
|
||||||
c.WaitForPeers(t, minPeers, 30*time.Second)
|
c.WaitForCondition(t, "all connected peers visible", 30*time.Second,
|
||||||
|
func(nm *netmap.NetworkMap) bool {
|
||||||
|
seen := make(map[string]bool, len(nm.Peers))
|
||||||
|
for _, peer := range nm.Peers {
|
||||||
|
if hi := peer.Hostinfo(); hi.Valid() {
|
||||||
|
seen[hi.Hostname()] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, other := range connected {
|
||||||
|
if other != c && !seen[other.Name] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the new nodes can see each other.
|
// Verify the new nodes can see each other.
|
||||||
|
|||||||
@@ -224,12 +224,28 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo
|
|||||||
c1.WaitForCondition(t, "first grant policy update",
|
c1.WaitForCondition(t, "first grant policy update",
|
||||||
10*time.Second,
|
10*time.Second,
|
||||||
func(nm *netmap.NetworkMap) bool {
|
func(nm *netmap.NetworkMap) bool {
|
||||||
return c1.UpdateCount() > countC1
|
for _, rule := range nm.PacketFilter {
|
||||||
|
for _, dst := range rule.Dsts {
|
||||||
|
if dst.Ports.First == 22 && dst.Ports.Last == 22 {
|
||||||
|
return c1.UpdateCount() > countC1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
c2.WaitForCondition(t, "first grant policy update",
|
c2.WaitForCondition(t, "first grant policy update",
|
||||||
10*time.Second,
|
10*time.Second,
|
||||||
func(nm *netmap.NetworkMap) bool {
|
func(nm *netmap.NetworkMap) bool {
|
||||||
return c2.UpdateCount() > countC2
|
for _, rule := range nm.PacketFilter {
|
||||||
|
for _, dst := range rule.Dsts {
|
||||||
|
if dst.Ports.First == 22 && dst.Ports.Last == 22 {
|
||||||
|
return c2.UpdateCount() > countC2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// Capture the update count after first policy.
|
// Capture the update count after first policy.
|
||||||
@@ -270,12 +286,12 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo
|
|||||||
c1.WaitForCondition(t, "second grant policy update",
|
c1.WaitForCondition(t, "second grant policy update",
|
||||||
10*time.Second,
|
10*time.Second,
|
||||||
func(nm *netmap.NetworkMap) bool {
|
func(nm *netmap.NetworkMap) bool {
|
||||||
return c1.UpdateCount() > updateCountPhase1
|
return c1.UpdateCount() > updateCountPhase1 && hasCapMatches(nm.PacketFilter)
|
||||||
})
|
})
|
||||||
c2.WaitForCondition(t, "second grant policy update",
|
c2.WaitForCondition(t, "second grant policy update",
|
||||||
10*time.Second,
|
10*time.Second,
|
||||||
func(nm *netmap.NetworkMap) bool {
|
func(nm *netmap.NetworkMap) bool {
|
||||||
return c2.UpdateCount() > countC2
|
return c2.UpdateCount() > countC2 && hasCapMatches(nm.PacketFilter)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Verify the second policy added cap grants that weren't
|
// Verify the second policy added cap grants that weren't
|
||||||
@@ -751,25 +767,27 @@ func TestGrantViaSubnetFilterRules(t *testing.T) {
|
|||||||
// Critical: the router's [netmap.NetworkMap.PacketFilter] MUST contain rules with
|
// Critical: the router's [netmap.NetworkMap.PacketFilter] MUST contain rules with
|
||||||
// the via-steered subnet (10.0.0.0/24) as a destination.
|
// the via-steered subnet (10.0.0.0/24) as a destination.
|
||||||
// Without this, the router drops traffic forwarded through it.
|
// Without this, the router drops traffic forwarded through it.
|
||||||
routerNM := routerA.Netmap()
|
requireNetmapHolds(t, func(tt require.TestingT) {
|
||||||
require.NotNil(t, routerNM)
|
routerNM := routerA.Netmap()
|
||||||
require.NotNil(t, routerNM.PacketFilter,
|
require.NotNil(tt, routerNM)
|
||||||
"router PacketFilter should not be nil")
|
require.NotNil(tt, routerNM.PacketFilter,
|
||||||
|
"router PacketFilter should not be nil")
|
||||||
|
|
||||||
var foundSubnetDst bool
|
var foundSubnetDst bool
|
||||||
|
|
||||||
for _, m := range routerNM.PacketFilter {
|
for _, m := range routerNM.PacketFilter {
|
||||||
for _, dst := range m.Dsts {
|
for _, dst := range m.Dsts {
|
||||||
dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits())
|
dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits())
|
||||||
if route.Contains(dstPrefix.Addr()) && dstPrefix.Bits() >= route.Bits() {
|
if route.Contains(dstPrefix.Addr()) && dstPrefix.Bits() >= route.Bits() {
|
||||||
foundSubnetDst = true
|
foundSubnetDst = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, foundSubnetDst,
|
assert.True(tt, foundSubnetDst,
|
||||||
"router PacketFilter should contain destination rules for via-steered subnet 10.0.0.0/24; "+
|
"router PacketFilter should contain destination rules for via-steered subnet 10.0.0.0/24; "+
|
||||||
"without per-node filter compilation for via grants, these rules are missing")
|
"without per-node filter compilation for via grants, these rules are missing")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGrantViaSubnetBroaderDstFilterRules verifies that a via grant
|
// TestGrantViaSubnetBroaderDstFilterRules verifies that a via grant
|
||||||
@@ -868,25 +886,27 @@ func TestGrantViaSubnetBroaderDstFilterRules(t *testing.T) {
|
|||||||
|
|
||||||
// Router's PacketFilter must contain the broader grant dst as a
|
// Router's PacketFilter must contain the broader grant dst as a
|
||||||
// destination — that is what Tailscale SaaS emits for via grants.
|
// destination — that is what Tailscale SaaS emits for via grants.
|
||||||
routerNM := routerA.Netmap()
|
requireNetmapHolds(t, func(tt require.TestingT) {
|
||||||
require.NotNil(t, routerNM)
|
routerNM := routerA.Netmap()
|
||||||
require.NotNil(t, routerNM.PacketFilter,
|
require.NotNil(tt, routerNM)
|
||||||
"router PacketFilter should not be nil")
|
require.NotNil(tt, routerNM.PacketFilter,
|
||||||
|
"router PacketFilter should not be nil")
|
||||||
|
|
||||||
var foundBroaderDst bool
|
var foundBroaderDst bool
|
||||||
|
|
||||||
for _, m := range routerNM.PacketFilter {
|
for _, m := range routerNM.PacketFilter {
|
||||||
for _, dst := range m.Dsts {
|
for _, dst := range m.Dsts {
|
||||||
dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits())
|
dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits())
|
||||||
if dstPrefix == broaderDst {
|
if dstPrefix == broaderDst {
|
||||||
foundBroaderDst = true
|
foundBroaderDst = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, foundBroaderDst,
|
assert.True(tt, foundBroaderDst,
|
||||||
"router PacketFilter should contain destination rules for the broader grant dst 10.0.0.0/8; "+
|
"router PacketFilter should contain destination rules for the broader grant dst 10.0.0.0/8; "+
|
||||||
"the via gate requires advertised-route overlap, and the emitted prefix is the dst")
|
"the via gate requires advertised-route overlap, and the emitted prefix is the dst")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGrantViaExitNodeNoFilterRules verifies wire-format SaaS compat:
|
// TestGrantViaExitNodeNoFilterRules verifies wire-format SaaS compat:
|
||||||
|
|||||||
@@ -110,20 +110,28 @@ func TestPolicyChanges(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
h := servertest.NewHarness(t, 2)
|
h := servertest.NewHarness(t, 2)
|
||||||
|
|
||||||
// Apply policy twice and verify updates arrive both times.
|
// Change the rule each time; reapplying a policy is a no-op.
|
||||||
for round := range 2 {
|
for round, port := range []uint16{22, 23} {
|
||||||
countBefore := h.Client(0).UpdateCount()
|
countBefore := h.Client(0).UpdateCount()
|
||||||
|
|
||||||
h.ChangePolicy(t, []byte(`{
|
h.ChangePolicy(t, fmt.Appendf(nil, `{
|
||||||
"acls": [
|
"acls": [
|
||||||
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
|
{"action": "accept", "src": ["*"], "dst": ["*:%d"]}
|
||||||
]
|
]
|
||||||
}`))
|
}`, port))
|
||||||
|
|
||||||
h.Client(0).WaitForCondition(t, "update after policy change",
|
h.Client(0).WaitForCondition(t, "update after policy change",
|
||||||
10*time.Second,
|
10*time.Second,
|
||||||
func(nm *netmap.NetworkMap) bool {
|
func(nm *netmap.NetworkMap) bool {
|
||||||
return h.Client(0).UpdateCount() > countBefore
|
for _, rule := range nm.PacketFilter {
|
||||||
|
for _, dst := range rule.Dsts {
|
||||||
|
if dst.Ports.First == port && dst.Ports.Last == port {
|
||||||
|
return h.Client(0).UpdateCount() > countBefore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Logf("round %d: update received", round)
|
t.Logf("round %d: update received", round)
|
||||||
|
|||||||
@@ -385,9 +385,18 @@ func TestRaceConnectDuringGracePeriod(t *testing.T) {
|
|||||||
surviving := originals[n/2:]
|
surviving := originals[n/2:]
|
||||||
allActive := append(surviving, replacements...)
|
allActive := append(surviving, replacements...)
|
||||||
|
|
||||||
for _, c := range allActive {
|
requireNetmapHolds(t, func(tt require.TestingT) {
|
||||||
c.WaitForPeers(t, len(allActive)-1, 30*time.Second)
|
for _, client := range allActive {
|
||||||
}
|
for _, peer := range allActive {
|
||||||
|
if client == peer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := client.PeerByName(peer.Name)
|
||||||
|
assert.True(tt, found, "%s must see active peer %s", client.Name, peer.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
servertest.AssertConsistentState(t, allActive)
|
servertest.AssertConsistentState(t, allActive)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ func NewServer(tb testing.TB, opts ...ServerOption) *TestServer {
|
|||||||
},
|
},
|
||||||
Taildrop: types.TaildropConfig{Enabled: sc.taildropEnabled},
|
Taildrop: types.TaildropConfig{Enabled: sc.taildropEnabled},
|
||||||
Tuning: types.Tuning{
|
Tuning: types.Tuning{
|
||||||
|
// Small fixtures rarely fill a batch; bound their write latency.
|
||||||
|
NodeStoreBatchTimeout: 10 * time.Millisecond,
|
||||||
BatchChangeDelay: sc.batchDelay,
|
BatchChangeDelay: sc.batchDelay,
|
||||||
BatcherWorkers: sc.batcherWorkers,
|
BatcherWorkers: sc.batcherWorkers,
|
||||||
NodeMapSessionBufferedChanSize: sc.bufferedChanSize,
|
NodeMapSessionBufferedChanSize: sc.bufferedChanSize,
|
||||||
|
|||||||
@@ -624,10 +624,20 @@ func TestStressChurn(t *testing.T) {
|
|||||||
servertest.WithUser(user))
|
servertest.WithUser(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the new set to converge.
|
// Old disconnected peers can satisfy the count before replacements
|
||||||
for _, c := range clients {
|
// arrive. Wait for every replacement identity to stay visible.
|
||||||
c.WaitForPeers(t, n-1, 30*time.Second)
|
requireNetmapHolds(t, func(tt require.TestingT) {
|
||||||
}
|
for _, client := range clients {
|
||||||
|
for _, peer := range clients {
|
||||||
|
if client == peer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := client.PeerByName(peer.Name)
|
||||||
|
assert.True(tt, found, "%s must see replacement %s", client.Name, peer.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
servertest.AssertSymmetricVisibility(t, clients)
|
servertest.AssertSymmetricVisibility(t, clients)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user