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

(cherry picked from commit 95ba787417)
This commit is contained in:
Kristoffer Dalby
2026-09-09 14:41:27 +00:00
parent 9fbf7b9b60
commit 938c2bd753
8 changed files with 96 additions and 108 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ type PolicyManager interface {
// MatchersForNode returns matchers for peer relationship determination (unreduced) // MatchersForNode returns matchers for peer relationship determination (unreduced)
MatchersForNode(node types.NodeView) ([]matcher.Match, error) MatchersForNode(node types.NodeView) ([]matcher.Match, error)
// BuildPeerMap constructs peer relationship maps for the given nodes // 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) SSHPolicy(baseURL string, node types.NodeView) (*tailcfg.SSHPolicy, error)
// SSHCheckParams resolves the SSH check period for a (src, dst) pair // SSHCheckParams resolves the SSH check period for a (src, dst) pair
// from the current policy, avoiding trust of client-provided URL params. // from the current policy, avoiding trust of client-provided URL params.
+2 -2
View File
@@ -90,8 +90,8 @@ func TestIssue3212AutogroupInternetExitVisibility(t *testing.T) {
map[types.NodeID]struct{}, map[types.NodeID]struct{},
len(peerMap[srcNode.ID]), len(peerMap[srcNode.ID]),
) )
for _, p := range peerMap[srcNode.ID] { for _, id := range peerMap[srcNode.ID] {
peerIDs[p.ID()] = struct{}{} peerIDs[id] = struct{}{}
} }
for _, exitName := range exitNames { for _, exitName := range exitNames {
+2 -6
View File
@@ -72,15 +72,11 @@ func TestIssue3233ViaInternetExitVisibility(t *testing.T) {
peerMap := pm.BuildPeerMap(nodes.ViewSlice()) peerMap := pm.BuildPeerMap(nodes.ViewSlice())
require.True(t, require.True(t,
slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool { slices.Contains(peerMap[aliceLaptop.ID], aliceExit.ID),
return n.ID() == aliceExit.ID
}),
"alice must see her tag:exit1 exit node as a peer") "alice must see her tag:exit1 exit node as a peer")
require.False(t, require.False(t,
slices.ContainsFunc(peerMap[aliceLaptop.ID], func(n types.NodeView) bool { slices.Contains(peerMap[aliceLaptop.ID], bobExit.ID),
return n.ID() == bobExit.ID
}),
"alice must not see bob's tag:exit2 exit node — via grant scopes to tag:exit1") "alice must not see bob's tag:exit2 exit node — via grant scopes to tag:exit1")
}) })
+7 -7
View File
@@ -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 // 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 // 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. // 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 { if pm == nil {
return 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 // Via grants require the per-node path because the global filter
// skips via grants (compileFilterRules: if len(grant.Via) > 0 { continue }). // skips via grants (compileFilterRules: if len(grant.Via) > 0 { continue }).
if !pm.needsPerNodeFilter { 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. // Build the map of all peers according to the matchers.
for i := range nodes.Len() { 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()] 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) || 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) { 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(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)) 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 // 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 // Pre-compute per-node matchers using unreduced compiled rules
// We need unreduced rules to determine peer relationships correctly. // 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) canIReachJ := hasFilterJ && nodeI.CanAccessWithRoutes(matchersJ, nodeJ, riI.subnet, riJ.subnet, riJ.isExit)
if canIAccessJ || canJAccessI || canJReachI || canIReachJ { if canIAccessJ || canJAccessI || canJReachI || canIReachJ {
ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ) ret[nodeI.ID()] = append(ret[nodeI.ID()], nodeJ.ID())
ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI) ret[nodeJ.ID()] = append(ret[nodeJ.ID()], nodeI.ID())
} }
} }
} }
+22 -49
View File
@@ -742,9 +742,8 @@ func TestAutogroupSelfWithOtherRules(t *testing.T) {
test1Peers := peerMap[test1Node.ID] test1Peers := peerMap[test1Node.ID]
// Verify test-1 can see the router (group:home -> tag:node-router rule) // Verify test-1 can see the router (group:home -> tag:node-router rule)
require.True(t, slices.ContainsFunc(test1Peers, func(n types.NodeView) bool { require.True(t, slices.Contains(test1Peers, test2RouterNode.ID),
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)")
}), "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) // Verify that test-1 has filter rules (including autogroup:self and tag:node-router access)
rules, err := pm.FilterForNode(test1Node.View()) rules, err := pm.FilterForNode(test1Node.View())
@@ -904,12 +903,12 @@ func TestTagPropagationToPeerMap(t *testing.T) {
// Check user2's peers - should include user1 // Check user2's peers - should include user1
user2Peers := initialPeerMap[user2Node.ID] user2Peers := initialPeerMap[user2Node.ID]
require.Len(t, user2Peers, 1, "user2 should have 1 peer initially (user1 with tag:web)") 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) // Check user1's peers - should include user2 (bidirectional ACL)
user1Peers := initialPeerMap[user1Node.ID] user1Peers := initialPeerMap[user1Node.ID]
require.Len(t, user1Peers, 1, "user1 should have 1 peer initially (user2)") 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 // Now change user1's tags: remove tag:web, keep only tag:internal
user1NodeUpdated := &types.Node{ user1NodeUpdated := &types.Node{
@@ -1036,17 +1035,15 @@ func TestAutogroupSelfWithAdminOverride(t *testing.T) {
// Admin should see the tagged server as a peer (via group:admin -> *:* rule) // Admin should see the tagged server as a peer (via group:admin -> *:* rule)
adminPeers := peerMap[adminNode.ID] adminPeers := peerMap[adminNode.ID]
require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(adminPeers, user1TaggedNode.ID),
return n.ID() == user1TaggedNode.ID "admin should see tagged server as peer via *:* rule (issue #2990)")
}), "admin should see tagged server as peer via *:* rule (issue #2990)")
// Tagged server should also see admin as a peer (symmetric visibility) // Tagged server should also see admin as a peer (symmetric visibility)
// Even though tagged server cannot ACCESS admin, it should still SEE admin // Even though tagged server cannot ACCESS admin, it should still SEE admin
// because admin CAN access it. This is required for proper network operation. // because admin CAN access it. This is required for proper network operation.
taggedPeers := peerMap[user1TaggedNode.ID] taggedPeers := peerMap[user1TaggedNode.ID]
require.True(t, slices.ContainsFunc(taggedPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(taggedPeers, adminNode.ID),
return n.ID() == adminNode.ID "tagged server should see admin as peer (symmetric visibility)")
}), "tagged server should see admin as peer (symmetric visibility)")
} }
// TestAutogroupSelfSymmetricVisibility verifies that peer visibility is symmetric: // TestAutogroupSelfSymmetricVisibility verifies that peer visibility is symmetric:
@@ -1109,16 +1106,14 @@ func TestAutogroupSelfSymmetricVisibility(t *testing.T) {
// Device A (user1) should see device B (tag:web) as peer // Device A (user1) should see device B (tag:web) as peer
aPeers := peerMap[deviceA.ID] aPeers := peerMap[deviceA.ID]
require.True(t, slices.ContainsFunc(aPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(aPeers, deviceB.ID),
return n.ID() == deviceB.ID "device A should see device B as peer (user1 -> tag:web rule)")
}), "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) // 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 // Even though B cannot ACCESS A, B should still SEE A as a peer
bPeers := peerMap[deviceB.ID] bPeers := peerMap[deviceB.ID]
require.True(t, slices.ContainsFunc(bPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(bPeers, deviceA.ID),
return n.ID() == deviceA.ID "device B should see device A as peer (symmetric visibility)")
}), "device B should see device A as peer (symmetric visibility)")
} }
// TestAutogroupSelfDoesNotBreakOtherUsersAccess reproduces the Discord scenario // TestAutogroupSelfDoesNotBreakOtherUsersAccess reproduces the Discord scenario
@@ -1254,11 +1249,7 @@ func TestAutogroupSelfDoesNotBreakOtherUsersAccess(t *testing.T) {
// Helper to check if node A sees node B // Helper to check if node A sees node B
canSee := func(a, b types.NodeID) bool { canSee := func(a, b types.NodeID) bool {
peers := peerMap[a] return slices.Contains(peerMap[a], b)
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
return n.ID() == b
})
} }
// Superadmin should see all tagged servers // Superadmin should see all tagged servers
@@ -1358,16 +1349,14 @@ func TestEmptyFilterNodesStillVisible(t *testing.T) {
// Admin should see the tagged server // Admin should see the tagged server
adminPeers := peerMap[adminDevice.ID] adminPeers := peerMap[adminDevice.ID]
require.True(t, slices.ContainsFunc(adminPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(adminPeers, taggedServer.ID),
return n.ID() == taggedServer.ID "admin should see tagged server")
}), "admin should see tagged server")
// Tagged server should see admin (symmetric visibility) // Tagged server should see admin (symmetric visibility)
// Even though the server has no outbound rules (empty filter) // Even though the server has no outbound rules (empty filter)
serverPeers := peerMap[taggedServer.ID] serverPeers := peerMap[taggedServer.ID]
require.True(t, slices.ContainsFunc(serverPeers, func(n types.NodeView) bool { require.True(t, slices.Contains(serverPeers, adminDevice.ID),
return n.ID() == adminDevice.ID "tagged server should see admin (symmetric visibility)")
}), "tagged server should see admin (symmetric visibility)")
} }
// TestAutogroupSelfCombinedWithTags verifies that autogroup:self combined with // TestAutogroupSelfCombinedWithTags verifies that autogroup:self combined with
@@ -1435,11 +1424,7 @@ func TestAutogroupSelfCombinedWithTags(t *testing.T) {
// Helper to check visibility // Helper to check visibility
canSee := func(a, b types.NodeID) bool { canSee := func(a, b types.NodeID) bool {
peers := peerMap[a] return slices.Contains(peerMap[a], b)
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
return n.ID() == b
})
} }
// Admin laptop should see: admin phone (autogroup:self) AND web server (tag:web) // Admin laptop should see: admin phone (autogroup:self) AND web server (tag:web)
@@ -1528,11 +1513,7 @@ func TestIssue2990SameUserTaggedDevice(t *testing.T) {
peerMap := pm.BuildPeerMap(nodes.ViewSlice()) peerMap := pm.BuildPeerMap(nodes.ViewSlice())
canSee := func(a, b types.NodeID) bool { canSee := func(a, b types.NodeID) bool {
peers := peerMap[a] return slices.Contains(peerMap[a], b)
return slices.ContainsFunc(peers, func(n types.NodeView) bool {
return n.ID() == b
})
} }
// node1 should see node2 (via group:admin -> *:* and symmetric visibility) // node1 should see node2 (via group:admin -> *:* and symmetric visibility)
@@ -2206,9 +2187,7 @@ func TestBuildPeerMap_AutogroupInternetMakesExitNodeVisible(t *testing.T) {
peerMap := pm.BuildPeerMap(nodes.ViewSlice()) peerMap := pm.BuildPeerMap(nodes.ViewSlice())
require.True(t, require.True(t,
slices.ContainsFunc(peerMap[aliceNode.ID], func(n types.NodeView) bool { slices.Contains(peerMap[aliceNode.ID], exitNode.ID),
return n.ID() == exitNode.ID
}),
"alice should see the exit node as a peer when an ACL grants autogroup:internet (#3212)") "alice should see the exit node as a peer when an ACL grants autogroup:internet (#3212)")
_, matchers := pm.Filter() _, matchers := pm.Filter()
@@ -2528,12 +2507,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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
pm, err := NewPolicyManager( pm, err := NewPolicyManager(
@@ -2544,10 +2517,10 @@ func TestPeerRelayGrantMakesRelayVisible(t *testing.T) {
peerMap := pm.BuildPeerMap(tt.nodes.ViewSlice()) peerMap := pm.BuildPeerMap(tt.nodes.ViewSlice())
for _, srcID := range tt.srcIDs { 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", "node %d must see relay %d via cap/relay alone",
srcID, tt.relayID) 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", "relay %d must see node %d via cap/relay alone",
tt.relayID, srcID) tt.relayID, srcID)
} }
+28 -9
View File
@@ -148,13 +148,18 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc
type Snapshot struct { type Snapshot struct {
// nodesByID is the main source of truth for nodes. // nodesByID is the main source of truth for nodes.
nodesByID map[types.NodeID]types.Node 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 // calculated from nodesByID
nodesByNodeKey map[key.NodePublic]types.NodeView nodesByNodeKey map[key.NodePublic]types.NodeView
nodesByMachineKey map[key.MachinePublic]map[types.UserID]types.NodeView nodesByMachineKey map[key.MachinePublic]map[types.UserID]types.NodeView
peersByNode map[types.NodeID][]types.NodeView // peersByNode stores immutable adjacency as IDs; ListPeers resolves the
nodesByUser map[types.UserID][]types.NodeView // corresponding views through nodeViewsByID.
allNodes []types.NodeView 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 // routes maps each prefix to its current primary advertiser. The
// previous assignment is carried over when still valid so 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. // with the relationships between nodes and their peers.
// This will typically be used to calculate which nodes can see each other // This will typically be used to calculate which nodes can see each other
// based on the current policy. // 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]. // work represents a single operation to be performed on the [NodeStore].
type work struct { type work struct {
@@ -617,14 +622,19 @@ func snapshotFromNodes(
defer timer.ObserveDuration() defer timer.ObserveDuration()
allNodes := make([]types.NodeView, 0, len(nodes)) allNodes := make([]types.NodeView, 0, len(nodes))
nodeViewsByID := make(map[types.NodeID]types.NodeView, len(nodes))
for _, n := range 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) routes, isPrimaryRoute := electPrimaryRoutes(nodes, prevRoutes)
newSnap := Snapshot{ newSnap := Snapshot{
nodesByID: nodes, nodesByID: nodes,
nodeViewsByID: nodeViewsByID,
allNodes: allNodes, allNodes: allNodes,
nodesByNodeKey: make(map[key.NodePublic]types.NodeView), nodesByNodeKey: make(map[key.NodePublic]types.NodeView),
nodesByMachineKey: make(map[key.MachinePublic]map[types.UserID]types.NodeView), nodesByMachineKey: make(map[key.MachinePublic]map[types.UserID]types.NodeView),
@@ -633,7 +643,7 @@ func snapshotFromNodes(
// it will use the list of all nodes, combined with the // it will use the list of all nodes, combined with the
// current policy to precalculate which nodes are peers and // current policy to precalculate which nodes are peers and
// can see each other. // can see each other.
peersByNode: func() map[types.NodeID][]types.NodeView { peersByNode: func() map[types.NodeID][]types.NodeID {
peersTimer := prometheus.NewTimer(nodeStorePeersCalculationDuration) peersTimer := prometheus.NewTimer(nodeStorePeersCalculationDuration)
defer peersTimer.ObserveDuration() defer peersTimer.ObserveDuration()
@@ -647,7 +657,7 @@ func snapshotFromNodes(
// Build nodesByUser, nodesByNodeKey, and nodesByMachineKey maps // Build nodesByUser, nodesByNodeKey, and nodesByMachineKey maps
for _, n := range nodes { for _, n := range nodes {
nodeView := n.View() nodeView := nodeViewsByID[n.ID]
userID := n.TypedUserID() userID := n.TypedUserID()
// Tagged nodes are owned by their tags, not a user, // Tagged nodes are owned by their tags, not a user,
@@ -658,7 +668,6 @@ func snapshotFromNodes(
newSnap.nodesByNodeKey[n.NodeKey] = nodeView newSnap.nodesByNodeKey[n.NodeKey] = nodeView
// Build machine key index
if newSnap.nodesByMachineKey[n.MachineKey] == nil { if newSnap.nodesByMachineKey[n.MachineKey] == nil {
newSnap.nodesByMachineKey[n.MachineKey] = make(map[types.UserID]types.NodeView) newSnap.nodesByMachineKey[n.MachineKey] = make(map[types.UserID]types.NodeView)
} }
@@ -886,7 +895,17 @@ func (s *NodeStore) ListPeers(id types.NodeID) views.Slice[types.NodeView] {
nodeStoreOperations.WithLabelValues("list_peers").Inc() 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. // PrimaryRouteFor returns the current primary advertiser for prefix.
+33 -33
View File
@@ -26,8 +26,8 @@ func TestSnapshotFromNodes(t *testing.T) {
name: "empty nodes", name: "empty nodes",
setupFunc: func() (map[types.NodeID]types.Node, PeersFunc) { setupFunc: func() (map[types.NodeID]types.Node, PeersFunc) {
nodes := make(map[types.NodeID]types.Node) nodes := make(map[types.NodeID]types.Node)
peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView { peersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
return make(map[types.NodeID][]types.NodeView) return make(map[types.NodeID][]types.NodeID)
} }
return nodes, peersFunc return nodes, peersFunc
@@ -79,9 +79,9 @@ func TestSnapshotFromNodes(t *testing.T) {
// Each node sees the other as peer (but not itself) // Each node sees the other as peer (but not itself)
assert.Len(t, snapshot.peersByNode[1], 1) 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.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) 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 // Odd nodes should only see other odd nodes as peers
require.Len(t, snapshot.peersByNode[1], 1) 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) 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 // Even nodes should only see other even nodes as peers
require.Len(t, snapshot.peersByNode[2], 1) 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) 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 // Peer functions
func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView { func allowAllPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
ret := make(map[types.NodeID][]types.NodeView, len(nodes)) ret := make(map[types.NodeID][]types.NodeID, len(nodes))
for _, node := range nodes { for _, node := range nodes {
var peers []types.NodeView var peers []types.NodeID
for _, n := range nodes { for _, n := range nodes {
if n.ID() != node.ID() { 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 return ret
} }
func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeView { func oddEvenPeersFunc(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
ret := make(map[types.NodeID][]types.NodeView, len(nodes)) ret := make(map[types.NodeID][]types.NodeID, len(nodes))
for _, node := range nodes { for _, node := range nodes {
var peers []types.NodeView var peers []types.NodeID
nodeIsOdd := node.ID()%2 == 1 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 // Only add peer if both are odd or both are even
if nodeIsOdd == peerIsOdd { 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 // Now both nodes should see each other as peers
assert.Len(t, snapshot.peersByNode[1], 1) 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.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) assert.Len(t, snapshot.nodesByUser[1], 2)
}, },
}, },
@@ -382,9 +382,9 @@ func TestNodeStoreOperations(t *testing.T) {
// Remaining nodes should see each other as peers // Remaining nodes should see each other as peers
assert.Len(t, snapshot.peersByNode[1], 1) 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.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 // User groupings updated
assert.Len(t, snapshot.nodesByUser[1], 1) // user1 now has only node 1 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 // Verify odd-even peer relationships
require.Len(t, snapshot.peersByNode[1], 1) 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) 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) 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) 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 // Even nodes should still see each other
require.Len(t, snapshot.peersByNode[2], 1) 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) 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])
}, },
}, },
}, },
@@ -1245,16 +1245,16 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
// This simulates how PolicyManager.BuildPeerMap works - it reads state // This simulates how PolicyManager.BuildPeerMap works - it reads state
// that can change between calls // that can change between calls
dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeView { dynamicPeersFunc := func(nodes []types.NodeView) map[types.NodeID][]types.NodeID {
ret := make(map[types.NodeID][]types.NodeView, len(nodes)) ret := make(map[types.NodeID][]types.NodeID, len(nodes))
if allowPeers { if allowPeers {
// Allow all peers // Allow all peers
for _, node := range nodes { for _, node := range nodes {
var peers []types.NodeView var peers []types.NodeID
for _, n := range nodes { for _, n := range nodes {
if n.ID() != node.ID() { if n.ID() != node.ID() {
peers = append(peers, n) peers = append(peers, n.ID())
} }
} }
@@ -1263,7 +1263,7 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
} else { } else {
// Allow no peers // Allow no peers
for _, node := range nodes { for _, node := range nodes {
ret[node.ID()] = []types.NodeView{} ret[node.ID()] = []types.NodeID{}
} }
} }
@@ -1285,8 +1285,8 @@ func TestRebuildPeerMapsWithChangedPeersFunc(t *testing.T) {
snapshot := store.data.Load() snapshot := store.data.Load()
require.Len(t, snapshot.peersByNode[1], 1, "node1 should have 1 peer initially") 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.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(2), snapshot.peersByNode[1][0])
require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0].ID()) require.Equal(t, types.NodeID(1), snapshot.peersByNode[2][0])
// Now "change the policy" by disabling peers // Now "change the policy" by disabling peers
allowPeers = false allowPeers = false
+1 -1
View File
@@ -281,7 +281,7 @@ func NewState(cfg *types.Config) (*State, error) {
// This moves the complex peer relationship logic into the policy package where it belongs. // This moves the complex peer relationship logic into the policy package where it belongs.
nodeStore := NewNodeStore( nodeStore := NewNodeStore(
nodes, 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)) return polMan.BuildPeerMap(views.SliceOf(nodes))
}, },
batchSize, batchSize,