mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-12 03:31:34 +09:00
policy,state: key the peer map by node ID
Adjacency becomes immutable, so a snapshot can resolve peers through its own fresh views instead of storing them. Updates #3417
This commit is contained in:
@@ -19,7 +19,7 @@ type PolicyManager interface {
|
||||
// MatchersForNode returns matchers for peer relationship determination (unreduced)
|
||||
MatchersForNode(node types.NodeView) ([]matcher.Match, error)
|
||||
// BuildPeerMap constructs peer relationship maps for the given nodes
|
||||
BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeView
|
||||
BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeID
|
||||
SSHPolicy(baseURL string, node types.NodeView) (*tailcfg.SSHPolicy, error)
|
||||
// SSHCheckParams resolves the SSH check period for a (src, dst) pair
|
||||
// from the current policy, avoiding trust of client-provided URL params.
|
||||
|
||||
@@ -90,8 +90,8 @@ func TestIssue3212AutogroupInternetExitVisibility(t *testing.T) {
|
||||
map[types.NodeID]struct{},
|
||||
len(peerMap[srcNode.ID]),
|
||||
)
|
||||
for _, p := range peerMap[srcNode.ID] {
|
||||
peerIDs[p.ID()] = struct{}{}
|
||||
for _, id := range peerMap[srcNode.ID] {
|
||||
peerIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
for _, exitName := range exitNames {
|
||||
|
||||
@@ -71,15 +71,11 @@ func TestIssue3233ViaInternetExitVisibility(t *testing.T) {
|
||||
peerMap := pm.BuildPeerMap(nodes.ViewSlice())
|
||||
|
||||
require.True(t,
|
||||
slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool {
|
||||
return n.ID() == aliceExit.ID
|
||||
}),
|
||||
slices.Contains(peerMap[aliceLaptop.ID], aliceExit.ID),
|
||||
"alice must see her tag:exit1 exit node as a peer")
|
||||
|
||||
require.False(t,
|
||||
slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool {
|
||||
return n.ID() == bobExit.ID
|
||||
}),
|
||||
slices.Contains(peerMap[aliceLaptop.ID], bobExit.ID),
|
||||
"alice must not see bob's tag:exit2 exit node — via grant scopes to tag:exit1")
|
||||
})
|
||||
|
||||
|
||||
@@ -603,7 +603,7 @@ func (pm *PolicyManager) Filter() ([]tailcfg.FilterRule, []matcher.Match) {
|
||||
// up with doing the full work for every node O(n^2), while this will reduce
|
||||
// the list as we see relationships while building the map, making it
|
||||
// O(n^2/2) in the end, but with less work per node.
|
||||
func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeView {
|
||||
func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeID {
|
||||
if pm == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -627,7 +627,7 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ
|
||||
// Via grants require the per-node path because the global filter
|
||||
// skips via grants (compileFilterRules: if len(grant.Via) > 0 { continue }).
|
||||
if !pm.needsPerNodeFilter {
|
||||
ret := make(map[types.NodeID][]types.NodeView, nodes.Len())
|
||||
ret := make(map[types.NodeID][]types.NodeID, nodes.Len())
|
||||
|
||||
// Build the map of all peers according to the matchers.
|
||||
for i := range nodes.Len() {
|
||||
@@ -639,8 +639,8 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ
|
||||
ri, rj := routeInfo[nodes.At(i).ID()], routeInfo[nodes.At(j).ID()]
|
||||
if nodes.At(i).CanAccessWithRoutes(pm.matchers, nodes.At(j), ri.subnet, rj.subnet, rj.isExit) ||
|
||||
nodes.At(j).CanAccessWithRoutes(pm.matchers, nodes.At(i), rj.subnet, ri.subnet, ri.isExit) {
|
||||
ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j))
|
||||
ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i))
|
||||
ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j).ID())
|
||||
ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i).ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,7 +649,7 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ
|
||||
}
|
||||
|
||||
// For autogroup:self or via grants, build per-node peer relationships
|
||||
ret := make(map[types.NodeID][]types.NodeView, nodes.Len())
|
||||
ret := make(map[types.NodeID][]types.NodeID, nodes.Len())
|
||||
|
||||
// Pre-compute per-node matchers using unreduced compiled rules
|
||||
// We need unreduced rules to determine peer relationships correctly.
|
||||
@@ -692,8 +692,8 @@ func (pm *PolicyManager) BuildPeerMap(nodes views.Slice[types.NodeView]) map[typ
|
||||
canIReachJ := hasFilterJ && nodeI.CanAccessWithRoutes(matchersJ, nodeJ, riI.subnet, riJ.subnet, riJ.isExit)
|
||||
|
||||
if canIAccessJ || canJAccessI || canJReachI || canIReachJ {
|
||||
ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ)
|
||||
ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI)
|
||||
ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ.ID())
|
||||
ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,9 +741,8 @@ func TestAutogroupSelfWithOtherRules(t *testing.T) {
|
||||
test1Peers := peerMap[test1Node.ID]
|
||||
|
||||
// Verify test-1 can see the router (group:home -> tag:node-router rule)
|
||||
require.True(t, slices.ContainsFunc(test1Peers, func(n types.NodeView) bool {
|
||||
return n.ID() == test2RouterNode.ID
|
||||
}), "test-1 should see test-2's router via group:home -> tag:node-router rule, even when autogroup:self rule exists (issue #2838)")
|
||||
require.True(t, slices.Contains(test1Peers, test2RouterNode.ID),
|
||||
"test-1 should see test-2's router via group:home -> tag:node-router rule, even when autogroup:self rule exists (issue #2838)")
|
||||
|
||||
// Verify that test-1 has filter rules (including autogroup:self and tag:node-router access)
|
||||
rules, err := pm.FilterForNode(test1Node.View())
|
||||
@@ -903,12 +902,12 @@ func TestTagPropagationToPeerMap(t *testing.T) {
|
||||
// Check user2's peers - should include user1
|
||||
user2Peers := initialPeerMap[user2Node.ID]
|
||||
require.Len(t, user2Peers, 1, "user2 should have 1 peer initially (user1 with tag:web)")
|
||||
require.Equal(t, user1Node.ID, user2Peers[0].ID(), "user2's peer should be user1")
|
||||
require.Equal(t, user1Node.ID, user2Peers[0], "user2's peer should be user1")
|
||||
|
||||
// Check user1's peers - should include user2 (bidirectional ACL)
|
||||
user1Peers := initialPeerMap[user1Node.ID]
|
||||
require.Len(t, user1Peers, 1, "user1 should have 1 peer initially (user2)")
|
||||
require.Equal(t, user2Node.ID, user1Peers[0].ID(), "user1's peer should be user2")
|
||||
require.Equal(t, user2Node.ID, user1Peers[0], "user1's peer should be user2")
|
||||
|
||||
// Now change user1's tags: remove tag:web, keep only tag:internal
|
||||
user1NodeUpdated := &types.Node{
|
||||
@@ -1035,17 +1034,15 @@ func TestAutogroupSelfWithAdminOverride(t *testing.T) {
|
||||
|
||||
// Admin should see the tagged server as a peer (via group:admin -> *:* rule)
|
||||
adminPeers := peerMap[adminNode.ID]
|
||||
require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == user1TaggedNode.ID
|
||||
}), "admin should see tagged server as peer via *:* rule (issue #2990)")
|
||||
require.True(t, slices.Contains(adminPeers, user1TaggedNode.ID),
|
||||
"admin should see tagged server as peer via *:* rule (issue #2990)")
|
||||
|
||||
// Tagged server should also see admin as a peer (symmetric visibility)
|
||||
// Even though tagged server cannot ACCESS admin, it should still SEE admin
|
||||
// because admin CAN access it. This is required for proper network operation.
|
||||
taggedPeers := peerMap[user1TaggedNode.ID]
|
||||
require.True(t, slices.ContainsFunc(taggedPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == adminNode.ID
|
||||
}), "tagged server should see admin as peer (symmetric visibility)")
|
||||
require.True(t, slices.Contains(taggedPeers, adminNode.ID),
|
||||
"tagged server should see admin as peer (symmetric visibility)")
|
||||
}
|
||||
|
||||
// TestAutogroupSelfSymmetricVisibility verifies that peer visibility is symmetric:
|
||||
@@ -1108,16 +1105,14 @@ func TestAutogroupSelfSymmetricVisibility(t *testing.T) {
|
||||
|
||||
// Device A (user1) should see device B (tag:web) as peer
|
||||
aPeers := peerMap[deviceA.ID]
|
||||
require.True(t, slices.ContainsFunc(aPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == deviceB.ID
|
||||
}), "device A should see device B as peer (user1 -> tag:web rule)")
|
||||
require.True(t, slices.Contains(aPeers, deviceB.ID),
|
||||
"device A should see device B as peer (user1 -> tag:web rule)")
|
||||
|
||||
// Device B (tag:web) should ALSO see device A as peer (symmetric visibility)
|
||||
// Even though B cannot ACCESS A, B should still SEE A as a peer
|
||||
bPeers := peerMap[deviceB.ID]
|
||||
require.True(t, slices.ContainsFunc(bPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == deviceA.ID
|
||||
}), "device B should see device A as peer (symmetric visibility)")
|
||||
require.True(t, slices.Contains(bPeers, deviceA.ID),
|
||||
"device B should see device A as peer (symmetric visibility)")
|
||||
}
|
||||
|
||||
// TestAutogroupSelfDoesNotBreakOtherUsersAccess reproduces the Discord scenario
|
||||
@@ -1253,11 +1248,7 @@ func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) {
|
||||
|
||||
// Helper to check if node A sees node B
|
||||
canSee := func(a, b types.NodeID) bool {
|
||||
peers := peerMap[a]
|
||||
|
||||
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
|
||||
return n.ID() == b
|
||||
})
|
||||
return slices.Contains(peerMap[a], b)
|
||||
}
|
||||
|
||||
// Superadmin should see all tagged servers
|
||||
@@ -1357,16 +1348,14 @@ func TestEmptyFilterNodesStillVisible(t *testing.T) {
|
||||
|
||||
// Admin should see the tagged server
|
||||
adminPeers := peerMap[adminDevice.ID]
|
||||
require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == taggedServer.ID
|
||||
}), "admin should see tagged server")
|
||||
require.True(t, slices.Contains(adminPeers, taggedServer.ID),
|
||||
"admin should see tagged server")
|
||||
|
||||
// Tagged server should see admin (symmetric visibility)
|
||||
// Even though the server has no outbound rules (empty filter)
|
||||
serverPeers := peerMap[taggedServer.ID]
|
||||
require.True(t, slices.ContainsFunc(serverPeers, func(n types.NodeView) bool {
|
||||
return n.ID() == adminDevice.ID
|
||||
}), "tagged server should see admin (symmetric visibility)")
|
||||
require.True(t, slices.Contains(serverPeers, adminDevice.ID),
|
||||
"tagged server should see admin (symmetric visibility)")
|
||||
}
|
||||
|
||||
// TestAutogroupSelfCombinedWithTags verifies that autogroup:self combined with
|
||||
@@ -1434,11 +1423,7 @@ func TestAutogroupSelfCombinedWithTags(t *testing.T) {
|
||||
|
||||
// Helper to check visibility
|
||||
canSee := func(a, b types.NodeID) bool {
|
||||
peers := peerMap[a]
|
||||
|
||||
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
|
||||
return n.ID() == b
|
||||
})
|
||||
return slices.Contains(peerMap[a], b)
|
||||
}
|
||||
|
||||
// Admin laptop should see: admin phone (autogroup:self) AND web server (tag:web)
|
||||
@@ -1527,11 +1512,7 @@ func TestIssue2990SameUserTaggedDevice(t *testing.T) {
|
||||
peerMap := pm.BuildPeerMap(nodes.ViewSlice())
|
||||
|
||||
canSee := func(a, b types.NodeID) bool {
|
||||
peers := peerMap[a]
|
||||
|
||||
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
|
||||
return n.ID() == b
|
||||
})
|
||||
return slices.Contains(peerMap[a], b)
|
||||
}
|
||||
|
||||
// node1 should see node2 (via group:admin -> *:* and symmetric visibility)
|
||||
@@ -2205,9 +2186,7 @@ func TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible(t *testing.T) {
|
||||
peerMap := pm.BuildPeerMap(nodes.ViewSlice())
|
||||
|
||||
require.True(t,
|
||||
slices.ContainsFunc(peerMap[aliceNode.ID], func(n types.NodeView) bool {
|
||||
return n.ID() == exitNode.ID
|
||||
}),
|
||||
slices.Contains(peerMap[aliceNode.ID], exitNode.ID),
|
||||
"alice should see the exit node as a peer when an ACL grants autogroup:internet (#3212)")
|
||||
|
||||
_, matchers := pm.Filter()
|
||||
@@ -2527,12 +2506,6 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
containsID := func(peers []types.NodeView, id types.NodeID) bool {
|
||||
return slices.ContainsFunc(peers, func(nv types.NodeView) bool {
|
||||
return nv.ID() == id
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pm, err := NewPolicyManager(
|
||||
@@ -2543,10 +2516,10 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) {
|
||||
peerMap := pm.BuildPeerMap(tt.nodes.ViewSlice())
|
||||
|
||||
for _, srcID := range tt.srcIDs {
|
||||
require.True(t, containsID(peerMap[srcID], tt.relayID),
|
||||
require.True(t, slices.Contains(peerMap[srcID], tt.relayID),
|
||||
"node %d must see relay %d via cap/relay alone",
|
||||
srcID, tt.relayID)
|
||||
require.True(t, containsID(peerMap[tt.relayID], srcID),
|
||||
require.True(t, slices.Contains(peerMap[tt.relayID], srcID),
|
||||
"relay %d must see node %d via cap/relay alone",
|
||||
tt.relayID, srcID)
|
||||
}
|
||||
|
||||
@@ -148,13 +148,18 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc
|
||||
type Snapshot struct {
|
||||
// nodesByID is the main source of truth for nodes.
|
||||
nodesByID map[types.NodeID]types.Node
|
||||
// nodeViewsByID resolves peer adjacency IDs to immutable views from this
|
||||
// snapshot without rebuilding every peer slice on each write.
|
||||
nodeViewsByID map[types.NodeID]types.NodeView
|
||||
|
||||
// calculated from nodesByID
|
||||
nodesByNodeKey map[key.NodePublic]types.NodeView
|
||||
nodesByMachineKey map[key.MachinePublic]map[types.UserID]types.NodeView
|
||||
peersByNode map[types.NodeID][]types.NodeView
|
||||
nodesByUser map[types.UserID][]types.NodeView
|
||||
allNodes []types.NodeView
|
||||
// peersByNode stores immutable adjacency as IDs; ListPeers resolves the
|
||||
// corresponding views through nodeViewsByID.
|
||||
peersByNode map[types.NodeID][]types.NodeID
|
||||
nodesByUser map[types.UserID][]types.NodeView
|
||||
allNodes []types.NodeView
|
||||
|
||||
// routes maps each prefix to its current primary advertiser. The
|
||||
// previous assignment is carried over when still valid so the
|
||||
@@ -167,7 +172,7 @@ type Snapshot struct {
|
||||
// with the relationships between nodes and their peers.
|
||||
// This will typically be used to calculate which nodes can see each other
|
||||
// based on the current policy.
|
||||
type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeView
|
||||
type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeID
|
||||
|
||||
// work represents a single operation to be performed on the [NodeStore].
|
||||
type work struct {
|
||||
@@ -608,14 +613,19 @@ func snapshotFromNodes(
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
allNodes := make([]types.NodeView, 0, len(nodes))
|
||||
nodeViewsByID := make(map[types.NodeID]types.NodeView, len(nodes))
|
||||
|
||||
for _, n := range nodes {
|
||||
allNodes = append(allNodes, n.View())
|
||||
nv := n.View()
|
||||
allNodes = append(allNodes, nv)
|
||||
nodeViewsByID[n.ID] = nv
|
||||
}
|
||||
|
||||
routes, isPrimaryRoute := electPrimaryRoutes(nodes, prevRoutes)
|
||||
|
||||
newSnap := Snapshot{
|
||||
nodesByID: nodes,
|
||||
nodeViewsByID: nodeViewsByID,
|
||||
allNodes: allNodes,
|
||||
nodesByNodeKey: make(map[key.NodePublic]types.NodeView),
|
||||
nodesByMachineKey: make(map[key.MachinePublic]map[types.UserID]types.NodeView),
|
||||
@@ -624,7 +634,7 @@ func snapshotFromNodes(
|
||||
// it will use the list of all nodes, combined with the
|
||||
// current policy to precalculate which nodes are peers and
|
||||
// can see each other.
|
||||
peersByNode: func() map[types.NodeID][]types.NodeView {
|
||||
peersByNode: func() map[types.NodeID][]types.NodeID {
|
||||
peersTimer := prometheus.NewTimer(nodeStorePeersCalculationDuration)
|
||||
defer peersTimer.ObserveDuration()
|
||||
|
||||
@@ -638,7 +648,7 @@ func snapshotFromNodes(
|
||||
|
||||
// Build nodesByUser, nodesByNodeKey, and nodesByMachineKey maps
|
||||
for _, n := range nodes {
|
||||
nodeView := n.View()
|
||||
nodeView := nodeViewsByID[n.ID]
|
||||
userID := n.TypedUserID()
|
||||
|
||||
// Tagged nodes are owned by their tags, not a user,
|
||||
@@ -649,7 +659,6 @@ func snapshotFromNodes(
|
||||
|
||||
newSnap.nodesByNodeKey[n.NodeKey] = nodeView
|
||||
|
||||
// Build machine key index
|
||||
if newSnap.nodesByMachineKey[n.MachineKey] == nil {
|
||||
newSnap.nodesByMachineKey[n.MachineKey] = make(map[types.UserID]types.NodeView)
|
||||
}
|
||||
@@ -882,7 +891,17 @@ func (s *NodeStore) ListPeers(id types.NodeID) views.Slice[types.NodeView] {
|
||||
|
||||
nodeStoreOperations.WithLabelValues("list_peers").Inc()
|
||||
|
||||
return views.SliceOf(s.data.Load().peersByNode[id])
|
||||
snapshot := s.data.Load()
|
||||
peerIDs := snapshot.peersByNode[id]
|
||||
peers := make([]types.NodeView, 0, len(peerIDs))
|
||||
|
||||
for _, peerID := range peerIDs {
|
||||
if peer, ok := snapshot.nodeViewsByID[peerID]; ok {
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
}
|
||||
|
||||
return views.SliceOf(peers)
|
||||
}
|
||||
|
||||
// PrimaryRouteFor returns the current primary advertiser for prefix.
|
||||
|
||||
@@ -26,8 +26,8 @@ func TestSnapshotFromNodes(t *testing.T) {
|
||||
name: "empty nodes",
|
||||
setupFunc: func() (map[types.NodeID]types.Node, PeersFunc) {
|
||||
nodes := make(map[types.NodeID]types.Node)
|
||||
peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView {
|
||||
return make(map[types.NodeID][]types.NodeView)
|
||||
peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
|
||||
return make(map[types.NodeID][]types.NodeID)
|
||||
}
|
||||
|
||||
return nodes, peersFunc
|
||||
@@ -79,9 +79,9 @@ func TestSnapshotFromNodes(t *testing.T) {
|
||||
|
||||
// Each node sees the other as peer (but not itself)
|
||||
assert.Len(t, snapshot.peersByNode[1], 1)
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID())
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0])
|
||||
assert.Len(t, snapshot.peersByNode[2], 1)
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID())
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0])
|
||||
assert.Len(t, snapshot.nodesByUser[1], 2)
|
||||
},
|
||||
},
|
||||
@@ -133,17 +133,17 @@ func TestSnapshotFromNodes(t *testing.T) {
|
||||
|
||||
// Odd nodes should only see other odd nodes as peers
|
||||
require.Len(t, snapshot.peersByNode[1], 1)
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID())
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0])
|
||||
|
||||
require.Len(t, snapshot.peersByNode[3], 1)
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID())
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0])
|
||||
|
||||
// Even nodes should only see other even nodes as peers
|
||||
require.Len(t, snapshot.peersByNode[2], 1)
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID())
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0])
|
||||
|
||||
require.Len(t, snapshot.peersByNode[4], 1)
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID())
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0])
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -190,14 +190,14 @@ func createTestNode(nodeID types.NodeID, userID uint, username, hostname string)
|
||||
|
||||
// Peer functions
|
||||
|
||||
func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView {
|
||||
ret := make(map[types.NodeID][]types.NodeView, len(nodes))
|
||||
func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
|
||||
ret := make(map[types.NodeID][]types.NodeID, len(nodes))
|
||||
for _, node := range nodes {
|
||||
var peers []types.NodeView
|
||||
var peers []types.NodeID
|
||||
|
||||
for _, n := range nodes {
|
||||
if n.ID() != node.ID() {
|
||||
peers = append(peers, n)
|
||||
peers = append(peers, n.ID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,10 +207,10 @@ func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView
|
||||
return ret
|
||||
}
|
||||
|
||||
func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView {
|
||||
ret := make(map[types.NodeID][]types.NodeView, len(nodes))
|
||||
func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
|
||||
ret := make(map[types.NodeID][]types.NodeID, len(nodes))
|
||||
for _, node := range nodes {
|
||||
var peers []types.NodeView
|
||||
var peers []types.NodeID
|
||||
|
||||
nodeIsOdd := node.ID()%2 == 1
|
||||
|
||||
@@ -223,7 +223,7 @@ func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView
|
||||
|
||||
// Only add peer if both are odd or both are even
|
||||
if nodeIsOdd == peerIsOdd {
|
||||
peers = append(peers, n)
|
||||
peers = append(peers, n.ID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +313,9 @@ func TestNodeStoreOperations(t *testing.T) {
|
||||
|
||||
// Now both nodes should see each other as peers
|
||||
assert.Len(t, snapshot.peersByNode[1], 1)
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID())
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0])
|
||||
assert.Len(t, snapshot.peersByNode[2], 1)
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID())
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0])
|
||||
assert.Len(t, snapshot.nodesByUser[1], 2)
|
||||
},
|
||||
},
|
||||
@@ -382,9 +382,9 @@ func TestNodeStoreOperations(t *testing.T) {
|
||||
|
||||
// Remaining nodes should see each other as peers
|
||||
assert.Len(t, snapshot.peersByNode[1], 1)
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID())
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0])
|
||||
assert.Len(t, snapshot.peersByNode[3], 1)
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID())
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0])
|
||||
|
||||
// User groupings updated
|
||||
assert.Len(t, snapshot.nodesByUser[1], 1) // user1 now has only node 1
|
||||
@@ -475,16 +475,16 @@ func TestNodeStoreOperations(t *testing.T) {
|
||||
|
||||
// Verify odd-even peer relationships
|
||||
require.Len(t, snapshot.peersByNode[1], 1)
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0].ID())
|
||||
assert.Equal(t, types.NodeID(3), snapshot.peersByNode[1][0])
|
||||
|
||||
require.Len(t, snapshot.peersByNode[2], 1)
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID())
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0])
|
||||
|
||||
require.Len(t, snapshot.peersByNode[3], 1)
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0].ID())
|
||||
assert.Equal(t, types.NodeID(1), snapshot.peersByNode[3][0])
|
||||
|
||||
require.Len(t, snapshot.peersByNode[4], 1)
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID())
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0])
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -500,9 +500,9 @@ func TestNodeStoreOperations(t *testing.T) {
|
||||
|
||||
// Even nodes should still see each other
|
||||
require.Len(t, snapshot.peersByNode[2], 1)
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0].ID())
|
||||
assert.Equal(t, types.NodeID(4), snapshot.peersByNode[2][0])
|
||||
require.Len(t, snapshot.peersByNode[4], 1)
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0].ID())
|
||||
assert.Equal(t, types.NodeID(2), snapshot.peersByNode[4][0])
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1225,16 +1225,16 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
|
||||
|
||||
// This simulates how PolicyManager.BuildPeerMap works - it reads state
|
||||
// that can change between calls
|
||||
dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView {
|
||||
ret := make(map[types.NodeID][]types.NodeView, len(nodes))
|
||||
dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
|
||||
ret := make(map[types.NodeID][]types.NodeID, len(nodes))
|
||||
if allowPeers {
|
||||
// Allow all peers
|
||||
for _, node := range nodes {
|
||||
var peers []types.NodeView
|
||||
var peers []types.NodeID
|
||||
|
||||
for _, n := range nodes {
|
||||
if n.ID() != node.ID() {
|
||||
peers = append(peers, n)
|
||||
peers = append(peers, n.ID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1243,7 +1243,7 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
|
||||
} else {
|
||||
// Allow no peers
|
||||
for _, node := range nodes {
|
||||
ret[node.ID()] = []types.NodeView{}
|
||||
ret[node.ID()] = []types.NodeID{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1265,8 +1265,8 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
|
||||
snapshot := store.data.Load()
|
||||
require.Len(t, snapshot.peersByNode[1], 1, "node1 should have 1 peer initially")
|
||||
require.Len(t, snapshot.peersByNode[2], 1, "node2 should have 1 peer initially")
|
||||
require.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0].ID())
|
||||
require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID())
|
||||
require.Equal(t, types.NodeID(2), snapshot.peersByNode[1][0])
|
||||
require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0])
|
||||
|
||||
// Now "change the policy" by disabling peers
|
||||
allowPeers = false
|
||||
|
||||
@@ -272,7 +272,7 @@ func NewState(cfg *types.Config) (*State, error) {
|
||||
// This moves the complex peer relationship logic into the policy package where it belongs.
|
||||
nodeStore := NewNodeStore(
|
||||
nodes,
|
||||
func(nodes []types.NodeView) map[types.NodeID][]types.NodeView {
|
||||
func(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
|
||||
return polMan.BuildPeerMap(views.SliceOf(nodes))
|
||||
},
|
||||
batchSize,
|
||||
|
||||
Reference in New Issue
Block a user