policy,types: skip recompile when the user list is unchanged

SetUsers now also reports whether user-derived peer adjacency moved.

Updates #3417
This commit is contained in:
Kristoffer Dalby
2026-09-09 14:36:20 +00:00
parent 1bbe59b98d
commit be322e8ea7
6 changed files with 159 additions and 20 deletions
+21
View File
@@ -647,3 +647,24 @@ func TestConcurrentPreAuthKeyRegistrationSameMachineKey(t *testing.T) {
require.Equal(t, 1, s.ListNodes().Len(),
"concurrent registrations of one machine key must yield a single node")
}
// TestUpdatePolicyManagerUsersUnchangedKeepsSnapshot ensures re-sending the
// same user list does not rebuild peer adjacency, while a real user change
// does.
func TestUpdatePolicyManagerUsersUnchangedKeepsSnapshot(t *testing.T) {
_, s, _ := persistTestSetup(t)
t.Cleanup(func() { _ = s.Close() })
require.NoError(t, s.UpdatePolicyManagerUsersForTest())
before := s.nodeStore.data.Load()
require.NoError(t, s.UpdatePolicyManagerUsersForTest())
require.Same(t, before, s.nodeStore.data.Load(),
"unchanged users must not rebuild the peer map")
_, _, err := s.CreateUser(types.User{Name: "second"})
require.NoError(t, err)
require.NotSame(t, before, s.nodeStore.data.Load(),
"a user change must rebuild the peer map")
}
+11 -7
View File
@@ -2878,12 +2878,9 @@ func reauthChange(node types.NodeView, isRelogin, policyChanged bool) change.Cha
}
}
// updatePolicyManagerUsers updates the policy manager with current users.
// Returns true if the policy changed and notifications should be sent.
// TODO(kradalby): This is a temporary stepping stone, ultimately we should
// have the list already available so it could go much quicker. Alternatively
// the policy manager could have a remove or add list for users.
// updatePolicyManagerUsers refreshes the policy manager with current user data.
// updatePolicyManagerUsers pushes the current user list into the policy
// manager, rebuilds peer adjacency when user identity changed, and returns
// a PolicyChange when clients need a refresh.
func (s *State) updatePolicyManagerUsers() (change.Change, error) {
users, err := s.ListAllUsers()
if err != nil {
@@ -2892,13 +2889,20 @@ func (s *State) updatePolicyManagerUsers() (change.Change, error) {
log.Debug().Caller().Int("user.count", len(users)).Msg("policy manager user update initiated because user list modification detected")
changed, err := s.polMan.SetUsers(users)
changed, peerMapChanged, err := s.polMan.SetUsers(users)
if err != nil {
return change.Change{}, fmt.Errorf("updating policy manager users: %w", err)
}
log.Debug().Caller().Bool("policy.changed", changed).Msg("policy manager user update completed because SetUsers operation finished")
if peerMapChanged {
// User-driven matcher state changed: rebuild candidate adjacency
// so peer visibility reflects the new policy. Without this, the
// cached peersByNode stays stale until the next node write.
s.nodeStore.RebuildPeerMaps()
}
if changed {
return change.PolicyChange(), nil
}