[management] shift network map data store onto the read replica (#8302)

This commit is contained in:
Pascal Fischer
2026-10-25 22:34:44 +01:00
committed by GitHub
parent dd38fabc82
commit fdc51d6f95
22 changed files with 1095 additions and 47 deletions
@@ -6,9 +6,9 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
@@ -38,6 +38,7 @@ func setupL4Test(t *testing.T, customPortsSupported *bool) (*Manager, store.Stor
err = testStore.SaveAccount(ctx, &types.Account{
Id: testAccountID,
CreatedBy: testUserID,
Network: types.NewNetwork(),
Settings: &types.Settings{
PeerExposeEnabled: true,
PeerExposeGroups: []string{testGroupID},
@@ -363,6 +363,10 @@ func (m *Manager) persistNewService(ctx context.Context, accountID string, svc *
return fmt.Errorf("create service: %w", err)
}
if err := transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
return nil
})
}
@@ -534,6 +538,10 @@ func (m *Manager) persistNewEphemeralService(ctx context.Context, accountID, pee
return fmt.Errorf("create service: %w", err)
}
if err := transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
return nil
})
}
@@ -744,6 +752,10 @@ func (m *Manager) executeServiceUpdate(ctx context.Context, transaction store.St
return fmt.Errorf("update service: %w", err)
}
if err := transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
return nil
}
@@ -1027,6 +1039,10 @@ func (m *Manager) DeleteService(ctx context.Context, accountID, userID, serviceI
return fmt.Errorf("failed to delete service: %w", err)
}
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
return nil
})
if err != nil {
@@ -1069,6 +1085,12 @@ func (m *Manager) DeleteAllServices(ctx context.Context, accountID, userID strin
}
}
if len(services) > 0 {
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
}
return nil
})
if err != nil {
@@ -1425,6 +1447,10 @@ func (m *Manager) deletePeerService(ctx context.Context, accountID, peerID, serv
return fmt.Errorf("delete service: %w", err)
}
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
return nil
})
if err != nil {
@@ -1477,6 +1503,11 @@ func (m *Manager) deleteExpiredPeerService(ctx context.Context, accountID, peerI
if err = transaction.DeleteService(ctx, accountID, serviceID); err != nil {
return fmt.Errorf("delete service: %w", err)
}
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return fmt.Errorf("increment network serial: %w", err)
}
deleted = true
return nil
@@ -227,6 +227,13 @@ func TestCheckDomainAvailable_EdgeCases(t *testing.T) {
})
}
func networkSerial(t *testing.T, s store.Store, accountID string) uint64 {
t.Helper()
account, err := s.GetAccount(context.Background(), accountID)
require.NoError(t, err)
return account.Network.Serial
}
func TestPersistNewService(t *testing.T) {
ctx := context.Background()
accountID := "test-account"
@@ -254,6 +261,9 @@ func TestPersistNewService(t *testing.T) {
txMock.EXPECT().
CreateService(ctx, service).
Return(nil)
txMock.EXPECT().
IncrementNetworkSerial(ctx, accountID).
Return(nil)
return fn(txMock)
})
@@ -465,6 +475,9 @@ func TestDeletePeerService_SourcePeerValidation(t *testing.T) {
txMock.EXPECT().
DeleteService(ctx, accountID, serviceID).
Return(nil)
txMock.EXPECT().
IncrementNetworkSerial(ctx, accountID).
Return(nil)
return fn(txMock)
})
mockStore.EXPECT().
@@ -570,6 +583,9 @@ func TestDeletePeerService_SourcePeerValidation(t *testing.T) {
txMock.EXPECT().
DeleteService(ctx, accountID, serviceID).
Return(nil)
txMock.EXPECT().
IncrementNetworkSerial(ctx, accountID).
Return(nil)
return fn(txMock)
})
mockStore.EXPECT().
@@ -617,6 +633,9 @@ func TestDeletePeerService_SourcePeerValidation(t *testing.T) {
txMock.EXPECT().
DeleteService(ctx, accountID, serviceID).
Return(nil)
txMock.EXPECT().
IncrementNetworkSerial(ctx, accountID).
Return(nil)
return fn(txMock)
})
mockStore.EXPECT().
@@ -679,6 +698,7 @@ func setupIntegrationTest(t *testing.T) (*Manager, store.Store) {
err = testStore.SaveAccount(ctx, &types.Account{
Id: testAccountID,
CreatedBy: testUserID,
Network: types.NewNetwork(),
Settings: &types.Settings{
PeerExposeEnabled: true,
PeerExposeGroups: []string{testGroupID},
@@ -843,6 +863,7 @@ func TestCreateServiceFromPeer(t *testing.T) {
assert.Equal(t, rpservice.SourceEphemeral, persisted.Source, "source should be ephemeral")
assert.Equal(t, testPeerID, persisted.SourcePeer, "source peer should be set")
assert.NotNil(t, persisted.Meta.LastRenewedAt, "last renewed should be set")
assert.Equal(t, uint64(1), networkSerial(t, testStore, testAccountID), "network serial should be incremented")
})
t.Run("creates service with custom domain", func(t *testing.T) {
@@ -1049,8 +1070,10 @@ func TestDeleteService_DeletesEphemeralExpose(t *testing.T) {
svc, err := testStore.GetServiceByDomain(ctx, resp.Domain)
require.NoError(t, err)
serial := networkSerial(t, testStore, testAccountID)
err = mgr.DeleteService(ctx, testAccountID, testUserID, svc.ID)
require.NoError(t, err)
assert.Equal(t, serial+1, networkSerial(t, testStore, testAccountID), "network serial should be incremented")
count, err = mgr.store.CountEphemeralServicesByPeer(ctx, store.LockingStrengthNone, testAccountID, testPeerID)
require.NoError(t, err)
@@ -1065,7 +1088,7 @@ func TestDeleteService_DeletesEphemeralExpose(t *testing.T) {
func TestDeleteAllServices_DeletesEphemeralExposes(t *testing.T) {
ctx := context.Background()
mgr, _ := setupIntegrationTest(t)
mgr, testStore := setupIntegrationTest(t)
for i := range 3 {
_, err := mgr.CreateServiceFromPeer(ctx, testAccountID, testPeerID, &rpservice.ExposeServiceRequest{
@@ -1079,8 +1102,10 @@ func TestDeleteAllServices_DeletesEphemeralExposes(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, int64(3), count, "all ephemeral services should exist")
serial := networkSerial(t, testStore, testAccountID)
err = mgr.DeleteAllServices(ctx, testAccountID, testUserID)
require.NoError(t, err)
assert.Equal(t, serial+1, networkSerial(t, testStore, testAccountID), "network serial should be incremented")
count, err = mgr.store.CountEphemeralServicesByPeer(ctx, store.LockingStrengthNone, testAccountID, testPeerID)
require.NoError(t, err)
@@ -1223,8 +1248,10 @@ func TestDeleteExpiredPeerService_DeletesTargets(t *testing.T) {
require.Len(t, targets, 1, "ephemeral peer-exposed service should have exactly one persisted target before reaping")
expireEphemeralService(t, testStore, testAccountID, resp.Domain)
serial := networkSerial(t, testStore, testAccountID)
err = mgr.deleteExpiredPeerService(ctx, testAccountID, testPeerID, svcID)
require.NoError(t, err)
assert.Equal(t, serial+1, networkSerial(t, testStore, testAccountID), "network serial should be incremented")
_, err = testStore.GetServiceByDomain(ctx, resp.Domain)
require.Error(t, err, "expired peer-exposed service should be deleted")
@@ -1253,8 +1280,10 @@ func TestDeleteServiceFromPeer_DeletesTargets(t *testing.T) {
require.NoError(t, err)
require.Len(t, targets, 1, "ephemeral peer-exposed service should have exactly one persisted target before stopping")
serial := networkSerial(t, testStore, testAccountID)
err = mgr.StopServiceFromPeer(ctx, testAccountID, testPeerID, svcID)
require.NoError(t, err)
assert.Equal(t, serial+1, networkSerial(t, testStore, testAccountID), "network serial should be incremented")
_, err = testStore.GetServiceByDomain(ctx, resp.Domain)
require.Error(t, err, "stopped peer-exposed service should be deleted")
@@ -1521,3 +1550,30 @@ func TestReloadAllServicesForAccount_ContinuesAfterFailedService(t *testing.T) {
assert.Contains(t, err.Error(), "svc-broken")
assert.NotContains(t, err.Error(), "svc-healthy")
}
func TestServiceWrites_IncrementNetworkSerial(t *testing.T) {
ctx := context.Background()
mgr, testStore, _ := setupL4Test(t, nil)
created, err := mgr.CreateService(ctx, testAccountID, testUserID, &rpservice.Service{
Name: "serial", Domain: "serial.test.netbird.io", Mode: rpservice.ModeHTTP, Enabled: true,
Targets: []*rpservice.Target{{
AccountID: testAccountID, TargetId: testPeerID,
TargetType: rpservice.TargetTypePeer, Protocol: "http", Port: 8080, Enabled: true,
}},
})
require.NoError(t, err)
serial := networkSerial(t, testStore, testAccountID)
assert.Equal(t, uint64(1), serial, "create should increment the network serial")
updated := created.Copy()
updated.Name = "serial renamed"
_, err = mgr.UpdateService(ctx, testAccountID, testUserID, updated)
require.NoError(t, err)
assert.Equal(t, serial+1, networkSerial(t, testStore, testAccountID), "update should increment the network serial")
serial++
require.NoError(t, mgr.SetStatus(ctx, testAccountID, created.ID, rpservice.StatusActive))
require.NoError(t, mgr.SetCertificateIssuedAt(ctx, testAccountID, created.ID))
assert.Equal(t, serial, networkSerial(t, testStore, testAccountID), "meta-only writes must not increment the network serial")
}