mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-29 15:59:10 +01:00
[management] shift network map data store onto the read replica (#8302)
This commit is contained in:
@@ -1825,10 +1825,10 @@ func (am *DefaultAccountManager) SyncUserJWTGroups(ctx context.Context, userAuth
|
||||
return fmt.Errorf("reconcile IPv6 for group changes: %w", err)
|
||||
}
|
||||
change.ChangedPeerIDs = append(change.ChangedPeerIDs, ipv6Changed...)
|
||||
}
|
||||
|
||||
if err = transaction.IncrementNetworkSerial(ctx, userAuth.AccountId); err != nil {
|
||||
return fmt.Errorf("error incrementing network serial: %w", err)
|
||||
}
|
||||
if err = transaction.IncrementNetworkSerial(ctx, userAuth.AccountId); err != nil {
|
||||
return fmt.Errorf("error incrementing network serial: %w", err)
|
||||
}
|
||||
|
||||
if snap, err = affectedpeers.Load(ctx, transaction, userAuth.AccountId, change); err != nil {
|
||||
@@ -2844,6 +2844,10 @@ func (am *DefaultAccountManager) savePeerIPUpdate(ctx context.Context, transacti
|
||||
return fmt.Errorf("save peer: %w", err)
|
||||
}
|
||||
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
eventMeta["old_ip"] = oldIP
|
||||
eventMeta["ip"] = newIP.String()
|
||||
am.StoreEvent(ctx, userID, peer.ID, accountID, activity.PeerIPUpdated, eventMeta)
|
||||
@@ -2884,7 +2888,8 @@ func (am *DefaultAccountManager) UpdatePeerIPv6(ctx context.Context, accountID,
|
||||
|
||||
// updatePeerIPv6InTransaction validates and applies an IPv6 address change within a store transaction.
|
||||
func (am *DefaultAccountManager) updatePeerIPv6InTransaction(ctx context.Context, transaction store.Store, accountID, peerID string, newIPv6 netip.Addr) (bool, error) {
|
||||
network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthShare, accountID)
|
||||
// Locked for update because the serial increment below writes the same row.
|
||||
network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthUpdate, accountID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get network: %w", err)
|
||||
}
|
||||
@@ -2928,6 +2933,10 @@ func (am *DefaultAccountManager) updatePeerIPv6InTransaction(ctx context.Context
|
||||
return false, fmt.Errorf("save peer: %w", err)
|
||||
}
|
||||
|
||||
if err := transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return false, fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3690,7 +3690,7 @@ func createManagerWithNetworkMapStore(t testing.TB) (*DefaultAccountManager, *up
|
||||
store, err := createStoreAt(t, dataDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
nmdataStore, err := networkmapdbfactory.NewNetworkMapDBStore(context.Background(), types.SqliteStoreEngine, dataDir, MockIntegratedValidator{}, newSettingsMockManager(t))
|
||||
nmdataStore, err := networkmapdbfactory.NewNetworkMapDBStore(context.Background(), types.SqliteStoreEngine, dataDir, MockIntegratedValidator{}, newSettingsMockManager(t), nil, nil)
|
||||
require.NoError(t, err)
|
||||
sqliteStore, ok := nmdataStore.Store.(*networkmap_sqlite.SqliteStore)
|
||||
require.True(t, ok, "network map store is %T, want *networkmap_sqlite.SqliteStore", nmdataStore.Store)
|
||||
@@ -5110,3 +5110,46 @@ func TestDefaultAccountManager_ExpirationJobsStopForDeletedAccount(t *testing.T)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func networkSerial(t *testing.T, manager *DefaultAccountManager, accountID string) uint64 {
|
||||
t.Helper()
|
||||
|
||||
network, err := manager.Store.GetAccountNetwork(context.Background(), store.LockingStrengthNone, accountID)
|
||||
require.NoError(t, err)
|
||||
return network.Serial
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_UpdatePeerIP_BumpsNetworkSerial(t *testing.T) {
|
||||
manager, _, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
|
||||
|
||||
newIP, err := types.AllocatePeerIP(netip.MustParsePrefix(account.Network.Net.String()), []netip.Addr{peer1.IP, peer2.IP, peer3.IP})
|
||||
require.NoError(t, err)
|
||||
|
||||
before := networkSerial(t, manager, account.Id)
|
||||
require.NoError(t, manager.UpdatePeerIP(context.Background(), account.Id, userID, peer1.ID, newIP))
|
||||
assert.Greater(t, networkSerial(t, manager, account.Id), before)
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_SyncUserJWTGroups_BumpsNetworkSerialWithoutPropagation(t *testing.T) {
|
||||
manager, _, err := createManager(t)
|
||||
require.NoError(t, err)
|
||||
|
||||
accountID, err := manager.GetAccountIDByUserID(context.Background(), auth.UserAuth{UserId: "jwt-user", Domain: "test.domain"})
|
||||
require.NoError(t, err)
|
||||
acc, err := manager.Store.GetAccount(context.Background(), accountID)
|
||||
require.NoError(t, err)
|
||||
acc.Settings.JWTGroupsEnabled = true
|
||||
acc.Settings.JWTGroupsClaimName = "idp-groups"
|
||||
acc.Settings.GroupsPropagationEnabled = false
|
||||
require.NoError(t, manager.Store.SaveAccount(context.Background(), acc))
|
||||
|
||||
before := networkSerial(t, manager, accountID)
|
||||
err = manager.SyncUserJWTGroups(context.Background(), auth.UserAuth{
|
||||
AccountId: accountID,
|
||||
Domain: "test.domain",
|
||||
UserId: "jwt-user",
|
||||
Groups: []string{"group1"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, networkSerial(t, manager, accountID), before)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
|
||||
return err
|
||||
}
|
||||
|
||||
updated, err := am.Store.MarkPeerConnectedIfNewerSession(ctx, accountID, peer.ID, sessionStartedAt)
|
||||
updated, err := am.writePeerStatus(ctx, accountID, peer, func(st store.Store) (bool, error) {
|
||||
return st.MarkPeerConnectedIfNewerSession(ctx, accountID, peer.ID, sessionStartedAt)
|
||||
})
|
||||
if err != nil {
|
||||
am.metrics.AccountManagerMetrics().CountPeerStatusUpdate(telemetry.PeerStatusConnect, telemetry.PeerStatusError)
|
||||
return err
|
||||
@@ -140,6 +142,28 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
|
||||
return nil
|
||||
}
|
||||
|
||||
// writePeerStatus runs a peer status write. The connection state of an embedded
|
||||
// proxy peer is part of the network map, so for those peers an applied write also
|
||||
// increments the account network serial in the same transaction.
|
||||
func (am *DefaultAccountManager) writePeerStatus(ctx context.Context, accountID string, peer *nbpeer.Peer, write func(store.Store) (bool, error)) (bool, error) {
|
||||
if !peer.ProxyMeta.Embedded {
|
||||
return write(am.Store)
|
||||
}
|
||||
|
||||
var updated bool
|
||||
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
|
||||
var err error
|
||||
if updated, err = write(transaction); err != nil || !updated {
|
||||
return err
|
||||
}
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return updated, err
|
||||
}
|
||||
|
||||
// schedulePeerExpirations arms the account's login/inactivity expiration
|
||||
// timers for an SSO peer that just connected.
|
||||
func (am *DefaultAccountManager) schedulePeerExpirations(ctx context.Context, accountID string, peer *nbpeer.Peer) error {
|
||||
@@ -179,7 +203,9 @@ func (am *DefaultAccountManager) MarkPeerDisconnected(ctx context.Context, peerP
|
||||
return err
|
||||
}
|
||||
|
||||
updated, err := am.Store.MarkPeerDisconnectedIfSameSession(ctx, accountID, peer.ID, sessionStartedAt)
|
||||
updated, err := am.writePeerStatus(ctx, accountID, peer, func(st store.Store) (bool, error) {
|
||||
return st.MarkPeerDisconnectedIfSameSession(ctx, accountID, peer.ID, sessionStartedAt)
|
||||
})
|
||||
if err != nil {
|
||||
am.metrics.AccountManagerMetrics().CountPeerStatusUpdate(telemetry.PeerStatusDisconnect, telemetry.PeerStatusError)
|
||||
return err
|
||||
@@ -349,7 +375,7 @@ func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, user
|
||||
}
|
||||
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("failed to increment network serial: %w", err)
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
return transaction.SavePeer(ctx, accountID, peer)
|
||||
@@ -618,7 +644,7 @@ func (am *DefaultAccountManager) deletePeerInTransaction(ctx context.Context, ac
|
||||
}
|
||||
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("failed to increment network serial: %w", err)
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -956,7 +982,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
|
||||
err = transaction.IncrementNetworkSerial(ctx, accountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to increment network serial: %w", err)
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
if ephemeral {
|
||||
@@ -1097,6 +1123,9 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync types.PeerSy
|
||||
if err = transaction.SavePeer(ctx, accountID, peer); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -1272,6 +1301,9 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
|
||||
if err = transaction.SavePeer(ctx, accountID, peer); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -3255,3 +3255,71 @@ func TestAddPeer_ExhaustedIPAllocationFails(t *testing.T) {
|
||||
require.Len(t, peers, 1)
|
||||
assert.Equal(t, netip.MustParseAddr("100.64.0.1"), peers[0].IP)
|
||||
}
|
||||
|
||||
func TestPeerWrites_BumpNetworkSerial(t *testing.T) {
|
||||
manager, _, account, peer1, peer2, _ := setupNetworkMapTest(t)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("SyncPeer with changed meta bumps, unchanged does not", func(t *testing.T) {
|
||||
current, err := manager.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer1.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
before := networkSerial(t, manager, account.Id)
|
||||
_, _, _, _, err = manager.SyncPeer(ctx, types.PeerSync{WireGuardPubKey: peer1.Key, Meta: current.Meta}, account.Id)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, before, networkSerial(t, manager, account.Id))
|
||||
|
||||
meta := current.Meta
|
||||
meta.Hostname = "renamed-host"
|
||||
_, _, _, _, err = manager.SyncPeer(ctx, types.PeerSync{WireGuardPubKey: peer1.Key, Meta: meta}, account.Id)
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, networkSerial(t, manager, account.Id), before)
|
||||
})
|
||||
|
||||
t.Run("LoginPeer with changed ssh key bumps, unchanged does not", func(t *testing.T) {
|
||||
current, err := manager.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer1.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
before := networkSerial(t, manager, account.Id)
|
||||
_, _, _, _, err = manager.LoginPeer(ctx, types.PeerLogin{WireGuardPubKey: peer1.Key, SSHKey: current.SSHKey, Meta: current.Meta})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, before, networkSerial(t, manager, account.Id))
|
||||
|
||||
_, _, _, _, err = manager.LoginPeer(ctx, types.PeerLogin{WireGuardPubKey: peer1.Key, SSHKey: "ssh-ed25519 AAAAnew", Meta: current.Meta})
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, networkSerial(t, manager, account.Id), before)
|
||||
})
|
||||
|
||||
t.Run("MarkPeerConnected and MarkPeerDisconnected", func(t *testing.T) {
|
||||
session := time.Now().UTC().UnixNano()
|
||||
|
||||
before := networkSerial(t, manager, account.Id)
|
||||
require.NoError(t, manager.MarkPeerConnected(ctx, peer2.Key, account.Id, session, nil))
|
||||
require.NoError(t, manager.MarkPeerDisconnected(ctx, peer2.Key, account.Id, session))
|
||||
assert.Equal(t, before, networkSerial(t, manager, account.Id), "regular peers must not bump")
|
||||
|
||||
expired, err := manager.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer2.ID)
|
||||
require.NoError(t, err)
|
||||
expired.Status.LoginExpired = true
|
||||
require.NoError(t, manager.Store.SavePeer(ctx, account.Id, expired))
|
||||
|
||||
session++
|
||||
require.NoError(t, manager.MarkPeerConnected(ctx, peer2.Key, account.Id, session, nil))
|
||||
afterExpired := networkSerial(t, manager, account.Id)
|
||||
assert.Equal(t, before, afterExpired, "login-expired reconnect writes no network map data")
|
||||
|
||||
embedded, err := manager.Store.GetPeerByID(ctx, store.LockingStrengthNone, account.Id, peer2.ID)
|
||||
require.NoError(t, err)
|
||||
embedded.Status.LoginExpired = false
|
||||
embedded.ProxyMeta.Embedded = true
|
||||
require.NoError(t, manager.Store.SavePeer(ctx, account.Id, embedded))
|
||||
|
||||
require.NoError(t, manager.MarkPeerDisconnected(ctx, peer2.Key, account.Id, session))
|
||||
afterDisconnect := networkSerial(t, manager, account.Id)
|
||||
assert.Greater(t, afterDisconnect, afterExpired, "embedded proxy disconnect must bump")
|
||||
|
||||
session++
|
||||
require.NoError(t, manager.MarkPeerConnected(ctx, peer2.Key, account.Id, session, nil))
|
||||
assert.Greater(t, networkSerial(t, manager, account.Id), afterDisconnect, "embedded proxy connect must bump")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -670,6 +670,12 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
|
||||
var updatedUsersInfo = make([]*types.UserInfo, 0, len(usersToSave))
|
||||
|
||||
if len(usersToSave) > 0 {
|
||||
if err = am.Store.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return nil, fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
userInfos, err := am.GetUsersFromAccount(ctx, accountID, initiatorUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -693,9 +699,6 @@ func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID,
|
||||
return nil, err
|
||||
}
|
||||
} else if len(usersToSave) > 0 {
|
||||
if err = am.Store.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return nil, fmt.Errorf("failed to increment network serial: %w", err)
|
||||
}
|
||||
go am.dispatchAffectedPeerIDs(ctx, accountID, affected.ids())
|
||||
}
|
||||
|
||||
@@ -1425,6 +1428,10 @@ func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountI
|
||||
return fmt.Errorf("failed to delete user: %s %w", targetUserInfo.ID, err)
|
||||
}
|
||||
|
||||
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
|
||||
return fmt.Errorf("increment network serial: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/roles"
|
||||
@@ -2352,3 +2353,24 @@ func TestUser_Operations_WithEmbeddedIDP(t *testing.T) {
|
||||
t.Logf("Duplicate email error: %v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_UserMutations_BumpNetworkSerial(t *testing.T) {
|
||||
manager, _, account, _, _, _ := setupNetworkMapTest(t)
|
||||
|
||||
account, err := manager.Store.GetAccount(context.Background(), account.Id)
|
||||
require.NoError(t, err)
|
||||
account.Users["regular-user"] = types.NewRegularUser("regular-user", "", "")
|
||||
require.NoError(t, manager.Store.SaveAccount(context.Background(), account))
|
||||
|
||||
ctx := nbcontext.WithRole(context.Background(), string(types.UserRoleOwner))
|
||||
|
||||
before := networkSerial(t, manager, account.Id)
|
||||
_, err = manager.SaveUser(ctx, account.Id, userID, &types.User{Id: "regular-user", Role: types.UserRoleUser, Blocked: true})
|
||||
require.NoError(t, err)
|
||||
afterSave := networkSerial(t, manager, account.Id)
|
||||
assert.Greater(t, afterSave, before, "SaveUser must bump the serial")
|
||||
|
||||
_, err = manager.deleteRegularUser(ctx, account.Id, userID, &types.UserInfo{ID: "regular-user"})
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, networkSerial(t, manager, account.Id), afterSave, "deleteRegularUser must bump the serial")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user