-
-
Notifications
You must be signed in to change notification settings - Fork 951
[management] Refactor network map controller #4789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
# Conflicts: # management/internals/controllers/network_map/controller/controller.go # management/internals/controllers/network_map/controller/repository.go # management/internals/controllers/network_map/interface.go # management/internals/server/boot.go # management/internals/server/controllers.go # management/internals/shared/grpc/server.go # management/server/account.go # management/server/peer.go # management/server/user.go
WalkthroughThe PR moves peer management into a new public Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client / gRPC
participant GRPC as NbGRPC Server
participant Controller as NetworkMap Controller
participant Ephemeral as EphemeralManager
participant Peers as peers.Manager
participant Store as Store
Client->>GRPC: Open stream / login
GRPC->>Controller: OnPeerConnected(ctx, accountId, peerId)
activate Controller
Controller->>Controller: create per-peer update channel
Controller->>Ephemeral: OnPeerConnected(ctx, accountId, peerId)
activate Ephemeral
Ephemeral->>Peers: schedule cleanup -> DeletePeers(accountId,[peerId],checkConnected=true)
deactivate Ephemeral
Controller-->>GRPC: return update channel
deactivate Controller
Note over Peers,Store: After ephemeral timeout
Peers->>Store: DeletePeers transaction (remove groups, policies, peer entries)
Store-->>Peers: commit/ack
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus review on:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
# Conflicts: # client/cmd/testutil_test.go # client/internal/engine_test.go # client/server/server_test.go # management/internals/controllers/network_map/controller/controller.go # management/internals/server/controllers.go # management/server/account/manager.go # management/server/account_test.go # management/server/dns_test.go # management/server/http/testing/testing_tools/channel/channel.go # management/server/management_proto_test.go # management/server/management_test.go # management/server/nameserver_test.go # management/server/peer_test.go # management/server/route_test.go # shared/management/client/client_test.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
management/server/http/handlers/peers/peers_handler_test.go (1)
283-311: Fix status code assertion to use the correct recorderThe handler writes to
recorder, but the status check usesrr(Line 307). That means this test will keep passing even if the handler returns the wrong status code. Also, the error message always printshttp.StatusOKinstead oftc.expectedStatus. You can simplify by removingrrand asserting onres.StatusCode:- rr := httptest.NewRecorder() - p := initTestMetaData(t, peer, peer1) @@ - res := recorder.Result() - defer res.Body.Close() - - if status := rr.Code; status != tc.expectedStatus { - t.Fatalf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } + res := recorder.Result() + defer res.Body.Close() + + if res.StatusCode != tc.expectedStatus { + t.Fatalf("handler returned wrong status code: got %v want %v", + res.StatusCode, tc.expectedStatus) + }management/server/user_test.go (1)
1428-1443: Subtest description contradicts new expectation for user deletion without linked peersIn
TestUserAccountPeersUpdate, the subtest"deleting user with no linked peers"still has a comment saying this “should not update account peers and not send peer update”, but the body now waits forpeerShouldReceiveUpdate(t, updMsg).Either the behavior has intentionally changed (and the comment should be updated to reflect that we now expect an update) or the expectation should still be
peerShouldNotReceiveUpdate. Please align the comment and assertion so the test clearly documents the intended behavior.management/internals/modules/peers/ephemeral/manager/ephemeral_test.go (1)
211-216: Fix invalidfor rangeover integer constant in batching testIn
TestCleanupSchedulingBehaviorIsBatched,ephemeralPeersis declared as an integer constant, but the loop usesfor i := range ephemeralPeers, which is not valid Go and will not compile. This should be a counted loop.Proposed fix:
const ( - ephemeralPeers = 10 + ephemeralPeers = 10 testLifeTime = 1 * time.Second testCleanupWindow = 100 * time.Millisecond ) // Add peers and disconnect them at slightly different times (within cleanup window) - for i := range ephemeralPeers { + for i := 0; i < ephemeralPeers; i++ { p := &nbpeer.Peer{ID: fmt.Sprintf("peer-%d", i), AccountID: account.Id, Ephemeral: true} mockStore.account.Peers[p.ID] = p mgr.OnPeerDisconnected(context.Background(), p) startTime = startTime.Add(testCleanupWindow / (ephemeralPeers * 2)) }Also applies to: 261-266
♻️ Duplicate comments (1)
management/server/http/handlers/peers/peers_handler.go (1)
222-222: Verify Connected status accuracy and confirm accessiblePeersCount=0 is intentional.Two observations:
Similar to line 76, the removal of
checkPeerStatusmeans the peer's Connected status is used directly. Ensure this is accurately maintained elsewhere.The
accessiblePeersCountparameter is hardcoded to0for all peers in the list view. This might be intentional for performance reasons (avoiding expensive calculations for each peer), but verify this aligns with API expectations.
🧹 Nitpick comments (10)
management/server/http/testing/testing_tools/channel/channel.go (1)
18-19: Reuse sharedpeers.Manager(and validator) instance for closer prod parityYou’re constructing two separate
peers.Managerinstances backed by the same store/permissions: one inline forNewEphemeralManagerand another aspeersManagerforNewAPIHandler. Functionally this works, but reusing a singlepeersManagerfor both wiring points would more closely mirror production and avoid redundant instances. Similarly, you already buildvalidatorMockabove; passing that intoNewControllerinstead of a freshserver.MockIntegratedValidator{}literal would keep the mocks consistent.Also applies to: 76-97
management/internals/server/server.go (1)
43-48: ExportedConfigfield works but relies on non‑nil invariantSwitching from a private
configto exportedConfigand updating call sites preserves existing behavior, butStartnow publicly relies onConfigandConfig.HttpConfigbeing non‑nil (dereferenced without checks). Since this struct is part of the public surface, consider either:
- Clearly documenting that
ConfigandConfig.HttpConfigmust be non‑nil before callingStart, or- Adding defensive nil checks with a clear error/log path.
This will make misuse harder if other packages start mutating or reusing
BaseServer.Also applies to: 71-83, 106-114, 127-131
management/internals/server/modules.go (1)
8-22: Config field usage and PeersManager DI look consistent
- Switching to
s.Configfor geolocation, account, and IDP managers is consistent withBaseServer’s publicConfigfield and removes the olds.configdependency.- The new
PeersManagerconstructor plusAfterInitwiring ofNetworkMapController,IntegratedValidator, andAccountManagercleanly centralizes peer-related dependencies and matches the new peers module design.No obvious issues as long as
AfterInitruns before peers manager is used in request handling, which appears to be the intended lifecycle.Also applies to: 24-35, 62-71, 74-82, 84-96
management/server/user_test.go (1)
10-15: Network map controller mocks are wired correctly; consider unifying gomock importsUsing
network_map.NewMockControllerwith agomock.NewController(t)and assertingOnPeersDeleted(gomock.Any(), gomock.Any(), gomock.Any())is called validates that user deletion paths trigger peer invalidation in the network map, which is valuable coverage.However, this file imports
go.uber.org/mock/gomock, while other tests in the repo still usegithub.com/golang/mock/gomock. Mixing the two gomock module paths can be confusing and risks type mismatches if mocks were generated against only one of them. It would be cleaner to standardize on a single gomock import path across the repo (whichever matches your generated mocks and go.mod).Also applies to: 744-756, 860-874
client/internal/engine_test.go (1)
31-35: Wiring ofpeers.ManagerandEphemeralManagerinto controller/server looks consistentThe new
peersManager := peers.NewManager(store, permissionsManager)andmanager.NewEphemeralManager(store, peersManager)usage instartManagementmatches the updatedcontroller.NewControllerandnbgrpc.NewServersignatures and keeps concerns localized to the test helper. Only optional nit: you now have bothiaandserver.MockIntegratedValidator{}in play; if you want test behavior closer to production, consider passing the same integrated validator instance into both the account manager and the controller/server, but this is not required for correctness here.Also applies to: 1603-1635
management/internals/controllers/network_map/interface.go (1)
31-40: Batch operations and connectivity hooks look good; consider normalizingaccountIDnamingThe extended
Controllerinterface (batch peer operations,CountStreams,OnPeerConnected/OnPeerDisconnected) is coherent with the new responsibilities and matches the mock signatures.Minor nit: parameter names mix
accountIdandaccountID(e.g.,OnPeersUpdatedandDisconnectPeersvsOnPeersAdded/OnPeersDeleted). For consistency with existing code and common Go style, you might want to standardize onaccountIDacross these methods.management/server/management_proto_test.go (1)
27-29: Consider stopping EphemeralManager in test cleanup to avoid leaked goroutines
ephemeralMgris created and passed intocontroller.NewController, but never stopped. If the manager starts background workers, they may outlive the test and make debugging harder.You can wrap the existing
cleanupfunction to stop the manager as well:- store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), testFile, t.TempDir()) + store, storeCleanup, err := store.NewTestStoreFromSQL(context.Background(), testFile, t.TempDir()) @@ - requestBuffer := NewAccountRequestBuffer(ctx, store) - ephemeralMgr := manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)) + requestBuffer := NewAccountRequestBuffer(ctx, store) + ephemeralMgr := manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)) + + cleanup := func() { + ephemeralMgr.Stop() + storeCleanup() + }Also applies to: 367-381
management/server/user.go (2)
959-1003: Guard batched network-map notifications when there are no peers to expire
expireAndUpdatePeersalways callsOnPeersUpdatedeven ifpeerIDsis empty. This adds unnecessary DB work and assumesGetPeersByIDstolerates an empty slice.You can short‑circuit when nothing was marked expired:
- for _, peer := range peers { + for _, peer := range peers { @@ - am.StoreEvent( + am.StoreEvent( ctx, peer.UserID, peer.ID, accountID, activity.PeerLoginExpired, peer.EventMeta(dnsDomain), ) - } - - err = am.networkMapController.OnPeersUpdated(ctx, accountID, peerIDs) - if err != nil { - return fmt.Errorf("notify network map controller of peer update: %w", err) - } - - if len(peerIDs) != 0 { - // this will trigger peer disconnect from the management service - log.Debugf("Expiring %d peers for account %s", len(peerIDs), accountID) - am.networkMapController.DisconnectPeers(ctx, accountID, peerIDs) - } + } + + if len(peerIDs) == 0 { + return nil + } + + if err := am.networkMapController.OnPeersUpdated(ctx, accountID, peerIDs); err != nil { + return fmt.Errorf("notify network map controller of peer update: %w", err) + } + + // this will trigger peer disconnect from the management service + log.Debugf("Expiring %d peers for account %s", len(peerIDs), accountID) + am.networkMapController.DisconnectPeers(ctx, accountID, peerIDs)
220-272: deleteRegularUser’s bool return value is no longer used; simplify API
deleteRegularUserreturns aboolthat’s now ignored by bothDeleteUserandDeleteRegularUsers, so the extra return value adds noise without behavior.Consider changing it to return only
errorand updating call sites:-func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountID, initiatorUserID string, targetUserInfo *types.UserInfo) (bool, error) { +func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountID, initiatorUserID string, targetUserInfo *types.UserInfo) error { @@ - if err != nil { - return false, err - } - - var peerIDs []string + if err != nil { + return err + } + + var peerIDs []string @@ - if err := am.networkMapController.OnPeersDeleted(ctx, accountID, peerIDs); err != nil { + if err := am.networkMapController.OnPeersDeleted(ctx, accountID, peerIDs); err != nil { log.WithContext(ctx).Errorf("failed to delete peers %s from network map: %v", peerIDs, err) } @@ - return updateAccountPeers, nil + return nilAnd at call sites:
- _, err = am.deleteRegularUser(ctx, accountID, initiatorUserID, userInfo) + err = am.deleteRegularUser(ctx, accountID, initiatorUserID, userInfo) @@ - _, err = am.deleteRegularUser(ctx, accountID, initiatorUserID, userInfo) + err = am.deleteRegularUser(ctx, accountID, initiatorUserID, userInfo)Also applies to: 1033-1085, 1087-1154
management/internals/controllers/network_map/controller/controller.go (1)
697-778: Tighten batched peer handlers (empty slices and repeated account loads)The new batch handlers are structurally sound, but a couple of small improvements would help:
OnPeersUpdated/OnPeersAdded/OnPeersDeletedrun even whenpeerIDsis empty; adding an earlyif len(peerIDs) == 0 { return nil }avoids unnecessary store and buffer work.- In
OnPeersAddedand the experimental branch ofOnPeersDeleted, you callGetAccountWithBackpressureonce per peer. You can fetch the account once and reuse it inside the loop for better efficiency:func (c *Controller) OnPeersAdded(ctx context.Context, accountID string, peerIDs []string) error { - for _, peerID := range peerIDs { - if c.experimentalNetworkMap(accountID) { - account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) + if len(peerIDs) == 0 { + return nil + } + + if c.experimentalNetworkMap(accountID) { + account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) + if err != nil { + return err + } + for _, peerID := range peerIDs { if err != nil { - return err + return err } - err = c.onPeerAddedUpdNetworkMapCache(account, peerID) if err != nil { return err } } } return c.bufferSendUpdateAccountPeers(ctx, accountID) }Similar restructuring applies to the experimental block in
OnPeersDeleted.Also applies to: 825-827
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (37)
client/cmd/testutil_test.go(2 hunks)client/internal/engine_test.go(2 hunks)client/server/server_test.go(2 hunks)go.mod(1 hunks)management/internals/controllers/network_map/controller/controller.go(6 hunks)management/internals/controllers/network_map/controller/repository.go(2 hunks)management/internals/controllers/network_map/interface.go(1 hunks)management/internals/controllers/network_map/interface_mock.go(2 hunks)management/internals/modules/peers/ephemeral/interface.go(1 hunks)management/internals/modules/peers/ephemeral/manager/ephemeral.go(5 hunks)management/internals/modules/peers/ephemeral/manager/ephemeral_test.go(5 hunks)management/internals/modules/peers/manager.go(1 hunks)management/internals/modules/peers/manager_mock.go(3 hunks)management/internals/server/boot.go(5 hunks)management/internals/server/controllers.go(3 hunks)management/internals/server/modules.go(4 hunks)management/internals/server/server.go(4 hunks)management/internals/shared/grpc/server.go(5 hunks)management/server/account.go(1 hunks)management/server/account/manager.go(0 hunks)management/server/account_test.go(2 hunks)management/server/dns_test.go(2 hunks)management/server/http/handler.go(1 hunks)management/server/http/handlers/peers/peers_handler.go(2 hunks)management/server/http/handlers/peers/peers_handler_test.go(1 hunks)management/server/http/testing/testing_tools/channel/channel.go(2 hunks)management/server/management_proto_test.go(3 hunks)management/server/management_test.go(2 hunks)management/server/mock_server/account_mock.go(0 hunks)management/server/nameserver_test.go(2 hunks)management/server/peer.go(6 hunks)management/server/peer_test.go(6 hunks)management/server/peers/manager.go(0 hunks)management/server/route_test.go(2 hunks)management/server/user.go(4 hunks)management/server/user_test.go(4 hunks)shared/management/client/client_test.go(3 hunks)
💤 Files with no reviewable changes (3)
- management/server/account/manager.go
- management/server/mock_server/account_mock.go
- management/server/peers/manager.go
🧰 Additional context used
🧬 Code graph analysis (27)
management/server/route_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
management/internals/controllers/network_map/controller/repository.go (1)
management/server/store/store.go (1)
LockingStrengthNone(47-47)
management/internals/server/server.go (2)
management/internals/server/config/config.go (1)
Config(37-60)encryption/letsencrypt.go (1)
CreateCertManager(12-31)
management/server/peer_test.go (4)
shared/management/proto/management.pb.go (3)
NetbirdConfig(1302-1315)NetbirdConfig(1330-1330)NetbirdConfig(1345-1347)management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
management/internals/modules/peers/ephemeral/manager/ephemeral_test.go (4)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/manager_mock.go (1)
NewMockManager(30-34)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/ephemeral/interface.go (1)
EphemeralLifeTime(11-11)
management/internals/modules/peers/manager.go (8)
management/internals/controllers/network_map/controller/controller.go (1)
Controller(39-65)management/server/integrations/integrated_validator/interface.go (1)
IntegratedValidator(12-23)management/internals/modules/peers/ephemeral/interface.go (2)
Manager(14-19)EphemeralLifeTime(11-11)management/server/permissions/modules/module.go (1)
Peers(7-7)management/server/permissions/operations/operation.go (1)
Read(7-7)shared/management/status/error.go (2)
Errorf(70-75)NewPermissionDeniedError(209-211)management/server/store/store.go (1)
LockingStrengthNone(47-47)management/server/activity/codes.go (2)
PolicyRemoved(39-39)PeerRemovedByUser(27-27)
management/internals/modules/peers/ephemeral/manager/ephemeral.go (3)
management/internals/modules/peers/ephemeral/interface.go (2)
Manager(14-19)EphemeralLifeTime(11-11)management/internals/modules/peers/manager.go (1)
Manager(23-32)management/server/activity/event.go (1)
SystemInitiator(8-8)
management/internals/modules/peers/manager_mock.go (3)
management/internals/modules/peers/manager.go (1)
Manager(23-32)management/server/integrations/integrated_validator/interface.go (1)
IntegratedValidator(12-23)management/internals/controllers/network_map/controller/controller.go (1)
Controller(39-65)
management/internals/controllers/network_map/controller/controller.go (4)
management/internals/modules/peers/ephemeral/interface.go (1)
Manager(14-19)management/internals/controllers/network_map/update_channel.go (1)
PeersUpdateManager(5-13)management/server/account/request_buffer.go (1)
RequestBuffer(9-11)management/internals/controllers/network_map/interface.go (1)
Controller(23-40)
management/server/user_test.go (3)
management/internals/controllers/network_map/interface_mock.go (1)
NewMockController(35-39)management/server/account.go (1)
DefaultAccountManager(66-109)management/server/integrated_validator.go (6)
MockIntegratedValidator(125-128)MockIntegratedValidator(153-155)MockIntegratedValidator(157-159)MockIntegratedValidator(161-163)MockIntegratedValidator(165-167)MockIntegratedValidator(169-171)
client/server/server_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
management/internals/controllers/network_map/interface.go (1)
management/internals/controllers/network_map/update_message.go (1)
UpdateMessage(7-9)
management/server/management_proto_test.go (4)
management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
management/internals/controllers/network_map/interface_mock.go (1)
management/internals/controllers/network_map/update_message.go (1)
UpdateMessage(7-9)
management/server/nameserver_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
management/server/http/handlers/peers/peers_handler.go (1)
shared/management/http/util/util.go (1)
WriteJSONObject(27-35)
shared/management/client/client_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
management/internals/server/controllers.go (6)
management/internals/server/server.go (1)
BaseServer(45-68)management/internals/controllers/network_map/update_channel.go (1)
PeersUpdateManager(5-13)management/internals/shared/grpc/token_mgr.go (1)
NewTimeBasedAuthSecretsManager(50-85)management/internals/server/config/config.go (3)
Config(37-60)TURNConfig(78-83)Relay(86-90)management/internals/modules/peers/ephemeral/manager/ephemeral.go (2)
EphemeralManager(39-50)NewEphemeralManager(53-61)management/internals/controllers/network_map/controller/controller.go (2)
Controller(39-65)NewController(75-111)
management/server/dns_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
management/server/account_test.go (4)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)management/server/account.go (1)
BuildManager(176-262)
management/server/management_test.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
client/cmd/testutil_test.go (4)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/server/account.go (1)
BuildManager(176-262)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
management/server/http/testing/testing_tools/channel/channel.go (3)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/internals/modules/peers/manager.go (1)
NewManager(43-48)
management/internals/shared/grpc/server.go (1)
management/internals/shared/grpc/token_mgr.go (1)
SecretsManager(27-32)
client/internal/engine_test.go (4)
management/internals/controllers/network_map/controller/controller.go (1)
NewController(75-111)management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
NewEphemeralManager(53-61)management/server/account.go (1)
BuildManager(176-262)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
management/internals/server/modules.go (5)
management/internals/server/server.go (1)
BaseServer(45-68)management/internals/server/container.go (1)
Create(6-10)management/internals/server/config/config.go (1)
Config(37-60)management/internals/modules/peers/manager.go (2)
NewManager(43-48)Manager(23-32)management/server/account.go (1)
BuildManager(176-262)
management/internals/server/boot.go (3)
management/internals/server/config/config.go (3)
Config(37-60)StoreConfig(169-171)ReverseProxy(174-192)management/internals/server/server.go (1)
NewServer(71-83)management/internals/shared/grpc/server.go (1)
NewServer(78-134)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Management / Benchmark (amd64, sqlite)
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Client / Unit (386)
- GitHub Check: Management / Benchmark (API) (amd64, postgres)
- GitHub Check: Management / Benchmark (amd64, postgres)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: Client / Unit
- GitHub Check: Client / Unit
- GitHub Check: Android / Build
- GitHub Check: release
- GitHub Check: Client / Unit
🔇 Additional comments (24)
management/server/http/handler.go (1)
24-24: Import path update verified successfully.The script confirms no remaining references to the old import path
github.com/netbirdio/netbird/management/server/peersexist in the codebase. The new pathgithub.com/netbirdio/netbird/management/internals/modules/peersis consistently used across production code, tests, and testing utilities. The refactoring is complete and correct.management/server/http/handlers/peers/peers_handler.go (1)
76-76: Verify that Connected status accuracy is maintained after removing checkPeerStatus.The removal of the
checkPeerStatusfunction cannot be fully verified from the codebase alone, as the function no longer exists. However, the current implementation loads peer status directly from the database (viaam.Store.GetPeerByID) and passes it unchanged to the response without any intermediate adjustments. Status is correctly loaded at the database layer and flows through the handler to the API response.To ensure no regression: confirm that peer Connected status is being maintained correctly in all scenarios where peers update their connection state, particularly around network map presence and peer lifecycle events.
go.mod (1)
67-67: The management-integrations version update is valid and compatible with the refactoring changes.Verification confirms:
Version format is valid: The pseudo-version
v0.0.0-20251114143509-4eff2374da63follows Go's standard format (base version + timestamp + commit hash).Dependencies are consistent: The version is present in both
go.modandgo.sumwith a valid hash, indicating successful resolution.Code integration is successful: EphemeralManager is properly wired with
peers.Manageracross 11 files. All usages follow the consistent pattern:NewEphemeralManager(store, peers.NewManager(store, permissionsManager)). This confirms the refactoring successfully integrated the required interfaces from the updated integrations module without breaking changes.Module exists and is documented: The Go module github.com/netbirdio/management-integrations is a real, documented package available on pkg.go.dev.
management/internals/modules/peers/ephemeral/interface.go (1)
5-12: Centralizing ephemeral lifetime looks goodExporting
EphemeralLifeTime = 10 * time.Minutehere is a clean way to share the lifetime across implementations and tests, assuming 10 minutes matches the previous behavior.management/internals/modules/peers/ephemeral/manager/ephemeral.go (1)
10-12: Ephemeral manager refactor viapeers.Managerlooks sound; verify delete semanticsThe move to inject
peers.Managerand useephemeral.EphemeralLifeTimeplus per‑account batching incleanupis clean and improves cohesion (all deletions now go through the standard peers path). Locking and timer rescheduling still look correct: deletions are computed underpeersLock, then the lock is released before callingDeletePeers.The only behavioral knobs worth re‑checking against the previous implementation are:
- Using
activity.SystemInitiatoras the initiator for these deletions.- Passing
checkConnected = truetoDeletePeers, to ensure we don’t regress by deleting connected peers (or conversely stop cleaning up peers that should be removed).If those match the old path, this refactor is in good shape.
Also applies to: 37-42, 53-60, 110-111, 184-195
management/server/account_test.go (1)
28-30: Test wiring to controller andBuildManagerlooks consistentInjecting
EphemeralManagerintocontroller.NewControllerand passing a concrete&config.Config{}intoBuildManageralign with the updated APIs and keep the test setup coherent. No issues spotted in this helper.Also applies to: 2964-2966
management/server/account.go (1)
2070-2073: Switched to batchOnPeersUpdatedwith proper error propagationUsing
OnPeersUpdated(ctx, peer.AccountID, []string{peerID})aligns with the new batch API, and wrapping the error gives good context to callers. No further issues here.management/server/nameserver_test.go (1)
16-17: Controller test wiring now correctly includes EphemeralManagerPassing
ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager))intocontroller.NewControllerupdates the tests to match the production constructor and keeps the dependencies consistent with the real server wiring. Looks good.Also applies to: 795-800
management/server/management_test.go (1)
25-26: Updated test server startup to match new controller/EphemeralManager API
startServernow buildsnetworkMapControllerwithephemeral_manager.NewEphemeralManager(str, peers.NewManager(str, permissionsManager)), matching the newNewControllersignature, and thenbgrpc.NewServercall remains consistent with its updated parameters. This keeps the integration test wiring aligned with production.Also applies to: 207-243
management/server/route_test.go (1)
19-21: Good job keeping tests aligned with the controller’s new dependency graph.Injecting the ephemeral manager here mirrors the production setup so the test harness exercises the refactored wiring accurately.
Also applies to: 296-297
management/server/dns_test.go (1)
15-17: Ephemeral peers manager wiring intoNewControllerlooks consistentThe test now constructs
ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager))and passes it intocontroller.NewController, matching the new constructor signature and using the samestoreandpermissionsManageras the rest of the test wiring. This keeps the DNS tests aligned with the refactored controller API without changing their behavior, since no ephemeral peers are created here.Also applies to: 220-231
management/internals/server/boot.go (2)
58-67: Config-based Store/EventStore wiring matchesBaseServer.ConfigThe refactor from
s.configtos.ConfiginStore()andEventStore()is consistent withBaseServerholding a*nbconfig.Config. Usings.Config.StoreConfig.Engine,s.Config.Datadir, ands.Config.DataStoreEncryptionKeykeeps all low-level storage and event-store setup driven from the single canonical config instance and preserves previous behavior aside from the naming.Also applies to: 69-92
131-140: Updated GRPC server TLS + constructor wiring is coherent with new signaturesUsing
s.Config.HttpConfigands.Config.Datadirfor LetsEncrypt/TLS configuration, and calling:srv, err := nbgrpc.NewServer( s.Config, s.AccountManager(), s.SettingsManager(), s.SecretsManager(), s.Metrics(), s.AuthManager(), s.IntegratedValidator(), s.NetworkMapController(), )matches the new
nbgrpc.NewServersignature and centralizes GRPC behavior around the same*nbconfig.Configinstance used elsewhere inBaseServer.Also applies to: 147-148
management/internals/server/controllers.go (2)
21-69: Peers update, secrets, auth, and ephemeral manager wiring align with new internals APIs
PeersUpdateManager()now returns thenetwork_map.PeersUpdateManagerinterface backed byupdate_channel.NewPeersUpdateManager(s.Metrics()), matching the interface definition.SecretsManager()correctly passes thatPeersUpdateManageralong withs.Config.TURNConfigands.Config.Relayintogrpc.NewTimeBasedAuthSecretsManager, keeping TURN/relay token refresh tied to the same update pipeline.AuthManager()now sources all auth-related values froms.Config.HttpConfigands.Config.GetAuthAudiences(), which matches the config layout.EphemeralManager()is now built frommanager.NewEphemeralManager(s.Store(), s.PeersManager()), matching the new internals module API and avoiding the older server-local ephemeral stack.This composition keeps the dependency graph coherent and moves ephemeral peer lifecycle into the shared internals layer as intended.
71-85: Network map controller construction matches extended constructor signature
NetworkMapController()now calls:nmapcontroller.NewController( context.Background(), s.Store(), s.Metrics(), s.PeersUpdateManager(), s.AccountRequestBuffer(), s.IntegratedValidator(), s.SettingsManager(), s.DNSDomain(), s.ProxyController(), s.EphemeralManager(), s.Config, )This is in the exact order expected by
NewControllerand wires in the newEphemeralManagerand DNS domain accessor cleanly. The addedDNSDomain()method simply exposess.dnsDomainand keeps controller construction consistent with the base server’s configuration.management/internals/shared/grpc/server.go (3)
53-75: Constructor and metrics now depend onnetworkMapControlleras the single source of stream state
Servernow holdsconfig,secretsManager,appMetrics,peerLocks,authManager, andnetworkMapController, andNewServerwires them as:func NewServer(config *nbconfig.Config, accountManager account.Manager, settingsManager settings.Manager, secretsManager SecretsManager, appMetrics telemetry.AppMetrics, authManager auth.Manager, integratedPeerValidator integrated_validator.IntegratedValidator, networkMapController network_map.Controller)The GRPC connected-streams metric is now registered via
networkMapController.CountStreams(), which matches the controller-centric design and aligns with the refactor away from a standalone peers update manager in this layer. The boot code passes the arguments in the correct order.Also applies to: 77-134
171-278: Sync/update loop correctly delegates lifecycle tonetworkMapControllerand secrets manager
- After initial sync, the server now calls
updates := s.networkMapController.OnPeerConnected(ctx, accountID, peer.ID)and passes that channel tohandleUpdates, so all subsequent network map updates flow through the controller.handleUpdatesoperates onchan *network_map.UpdateMessage, closes cleanly when the channel is closed or whensrv.Context()is done, and callscancelPeerRoutinesin both cases.cancelPeerRoutinesretains the existingaccountManager.OnPeerDisconnectedcall and addss.networkMapController.OnPeerDisconnected(ctx, accountID, peer.ID)pluss.secretsManager.CancelRefresh(peer.ID). Together withs.secretsManager.SetupRefresh(ctx, accountID, peer.ID)inSync, this keeps the TURN/relay refresh lifecycle keyed consistently on the peer ID and tied to the controller’s connection lifecycle.The new flow looks internally consistent; assuming
OnPeerConnected/OnPeerDisconnectedand the secrets manager share the same peer ID key space, no functional regressions are apparent.Also applies to: 281-345
613-637: DNS domain and config propagation through login and sync responses is consistent
prepareLoginResponsenow usess.networkMapController.GetDNSDomain(settings)ands.config.HttpConfigwhen buildingNetbirdConfigandPeerConfig, making DNS domain resolution the controller’s responsibility while still sourcing other fields froms.config.sendInitialSyncsimilarly passess.configands.config.HttpConfigintoToSyncResponse, along withs.networkMapController.GetDNSDomain(settings), so the same DNS domain logic is reused between login and sync.- Device/PKCE authorization flow handlers still read from
s.config.DeviceAuthorizationFlowands.config.PKCEAuthorizationFlowrespectively, with no behavioral changes besides the surrounding refactor.Overall, the GRPC handlers now consistently derive DNS domains from the controller while continuing to rely on the shared
*nbconfig.Configfor all other management-plane configuration.Also applies to: 669-719, 721-777, 779-833
management/server/peer.go (3)
103-146: BatchOnPeersUpdatedusage inMarkPeerConnectedandUpdatePeerimproves consistency and error handling
MarkPeerConnectednow callsam.networkMapController.OnPeersUpdated(ctx, accountID, []string{peer.ID})only when the login-expired flag actually changed, and propagates a wrapped error if the controller update fails. This keeps network map state aligned with the peer’s expiration status.UpdatePeersimilarly switches toOnPeersUpdatedwith a single-element slice and now returns a wrapped error if the controller cannot be notified, instead of silently succeeding.Both methods pass the correct
accountIDandpeer.IDand use contextual error messages, which will make diagnosing controller-side issues easier.Also applies to: 185-321
323-379: Deletion and addition now useOnPeersDeleted/OnPeersAddedbatch APIs
DeletePeercallsOnPeersDeleted(ctx, accountID, []string{peerID})after DB deletion and event storage, logging but not propagating errors. That keeps peer removal tolerant to cache/controller failures while still surfacing diagnostics.AddPeercallsOnPeersAdded(ctx, accountID, []string{newPeer.ID})once the new peer and its groups are committed, again logging but not returning an error if the network map cache update fails, and then callsGetValidatedPeerWithMapas before.The switch to slice-based APIs is semantically correct for single-peer operations and prepares the code for future batching without changing observable behavior.
Also applies to: 642-649
664-735: Sync/login flows now rely onOnPeersUpdatedwith proper error propagation
- In
SyncPeer, whenever status changes,sync.UpdateAccountPeersis true, or metadata/posture versioning implies a net map change, the code now callsOnPeersUpdated(ctx, accountID, []string{peer.ID})and returns a wrapped error on failure before building the validated peer/network map.- In
LoginPeer, the same pattern is used after the transactional update: if remote peers or posture state should change, it callsOnPeersUpdatedand propagates any error, before fetching the validated peer and map.These changes tighten the coupling between store mutations and controller cache updates, ensuring that callers see failures if the network map cannot be refreshed as required.
Also applies to: 756-867
management/internals/modules/peers/manager_mock.go (1)
7-16: Generated peers.Manager mock methods match the updated interfaceThe added imports and methods (
DeletePeers,SetAccountManager,SetIntegratedPeerValidator,SetNetworkMapControllerand their recorders) correctly mirror theManagerinterface signatures and follow standard gomock patterns for generated code. No issues from a correctness standpoint.Also applies to: 41-53, 115-149
management/internals/controllers/network_map/interface_mock.go (1)
60-85: MockController updates correctly mirror the extended Controller interfaceThe newly added mocks (
CountStreams, updatedDisconnectPeers, and the variousOnPeer*/OnPeers*methods) and their recorders are consistent with the updatedControllerinterface and follow the expected go.uber/gomock generation patterns. No issues from a correctness or usability standpoint.Also applies to: 147-213
management/internals/controllers/network_map/controller/controller.go (1)
39-47: Ephemeral peer lifecycle wiring through controller looks consistentThe added
EphemeralPeersManagerfield, constructor parameter, andOnPeerConnected/OnPeerDisconnectedhooks integrate ephemeral handling cleanly with stream creation/teardown and keeppeersUpdateManagerresponsibilities focused.Also applies to: 75-111, 113-137
| store := &MockStore{} | ||
| am := MockAccountManager{ | ||
| store: store, | ||
| } | ||
| ctrl := gomock.NewController(t) | ||
| peersManager := peers.NewMockManager(ctrl) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure gomock controllers are finished to assert expectations
All four tests create a gomock.Controller but never call Finish (via defer or t.Cleanup). This means expectations such as Times(1) in TestCleanupSchedulingBehaviorIsBatched may not actually be verified, weakening the tests.
Consider updating each test to register ctrl.Finish with the test:
ctrl := gomock.NewController(t)
+ t.Cleanup(ctrl.Finish)
peersManager := peers.NewMockManager(ctrl)Apply the same pattern in TestNewManager, TestNewManagerPeerConnected, TestNewManagerPeerDisconnected, and TestCleanupSchedulingBehaviorIsBatched.
Also applies to: 134-137, 175-178, 237-239
🤖 Prompt for AI Agents
In management/internals/modules/peers/ephemeral/manager/ephemeral_test.go around
lines 96-99 (and similarly for 134-137, 175-178, 237-239), the gomock.Controller
created for each test is not finished so expectations may not be verified; fix
each test by registering ctrl.Finish with the test lifecycle (e.g., call defer
ctrl.Finish() or t.Cleanup(ctrl.Finish)) immediately after creating the
controller so gomock validates expectations when the test ends.
| type managerImpl struct { | ||
| store store.Store | ||
| permissionsManager permissions.Manager | ||
| integratedPeerValidator integrated_validator.IntegratedValidator | ||
| accountManager account.Manager | ||
|
|
||
| networkMapController network_map.Controller | ||
| } | ||
|
|
||
| func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { | ||
| return &managerImpl{ | ||
| store: store, | ||
| permissionsManager: permissionsManager, | ||
| } | ||
| } | ||
|
|
||
| func (m *managerImpl) SetNetworkMapController(networkMapController network_map.Controller) { | ||
| m.networkMapController = networkMapController | ||
| } | ||
|
|
||
| func (m *managerImpl) SetIntegratedPeerValidator(integratedPeerValidator integrated_validator.IntegratedValidator) { | ||
| m.integratedPeerValidator = integratedPeerValidator | ||
| } | ||
|
|
||
| func (m *managerImpl) SetAccountManager(accountManager account.Manager) { | ||
| m.accountManager = accountManager | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Ensure peers.Manager is fully initialized before DeletePeers; consider nil guards and network‑map notification
DeletePeers assumes networkMapController, integratedPeerValidator, and accountManager are non‑nil:
m.networkMapController.GetDNSDomain(settings)m.integratedPeerValidator.PeerDeleted(...)m.accountManager.StoreEvent(...)
If a caller uses NewManager and forgets to call one of the Set* methods (e.g., the test wiring that constructs NewEphemeralManager(store, peers.NewManager(store, permissionsManager)) without setting these fields), any path that eventually calls DeletePeers will panic.
It would be safer to defensively guard at the top of DeletePeers:
func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
+ if m.networkMapController == nil || m.integratedPeerValidator == nil || m.accountManager == nil {
+ return fmt.Errorf("peers manager is not fully initialized (missing networkMapController/integratedPeerValidator/accountManager)")
+ }Separately, DeletePeers currently deletes peers from the store and emits events but doesn’t call any of the new network‑map batch APIs (OnPeersDeleted, DisconnectPeers, or BufferUpdateAccountPeers). Please confirm whether:
- Callers of
DeletePeersare responsible for notifying the network map controller themselves, or - This method should also trigger a network‑map refresh (e.g., collect successfully deleted
peerIDsand callnetworkMapController.OnPeersDeleted).
Right now, a direct DeletePeers call appears to leave connected peers and their network maps potentially stale until some other operation updates the account.
Also applies to: 96-162
I'll verify the review comment by examining the actual DeletePeers implementation and the scope of the concern.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the DeletePeers method implementation in manager.go
rg -n "func.*DeletePeers" management/internals/modules/peers/manager.go -A 50
Length of output: 1956
🏁 Script executed:
#!/bin/bash
# Also get the full context around lines 96-162 mentioned in "Also applies to"
sed -n '96,162p' management/internals/modules/peers/manager.goLength of output: 2061
🏁 Script executed:
#!/bin/bash
# Check if there are any nil checks on these fields anywhere in the file
rg -n "if.*networkMapController.*nil|if.*integratedPeerValidator.*nil|if.*accountManager.*nil" management/internals/modules/peers/manager.goLength of output: 108
🏁 Script executed:
#!/bin/bash
# Search for usages of SetNetworkMapController, SetIntegratedPeerValidator, SetAccountManager
# to see if they're always called before DeletePeers
rg -n "SetNetworkMapController|SetIntegratedPeerValidator|SetAccountManager" management/internals/modules/peers/ -B 2 -A 2Length of output: 5685
Add nil guards to DeletePeers and clarify network-map notification strategy
Verification confirms the review concerns are valid:
-
Unguarded nil dereferences:
DeletePeersdirectly accessesm.networkMapController(line 101),m.integratedPeerValidator(line 119), andm.accountManager(lines 139, 156) without checking if they are nil. If anySet*method is omitted during initialization, DeletePeers will panic. -
Network-map notification gap:
DeletePeerscallsm.networkMapController.GetDNSDomain(settings)at line 101 but never notifies it of peer deletions. It only stores events viaaccountManager.StoreEvent. Confirm whether the network-map controller should be notified of deletions (e.g., callOnPeersDeletedfor successfully deleted peers, or if callers handle that externally).
Recommend adding defensive nil checks at the start of DeletePeers:
func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
+ if m.networkMapController == nil || m.integratedPeerValidator == nil || m.accountManager == nil {
+ return fmt.Errorf("peers manager is not fully initialized")
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In management/internals/modules/peers/manager.go around lines 34 to 61 and for
DeletePeers (referenced at lines ~101, ~119, ~139, ~156), add defensive nil
checks for m.networkMapController, m.integratedPeerValidator, and
m.accountManager at the start of DeletePeers to avoid panics when Set* was not
called; decide and implement one behavior: if a component is required for
correct operation return a clear error immediately, otherwise treat it as
optional and skip related steps. After performing deletions, if
networkMapController is present call its notification method (e.g.,
OnPeersDeleted or equivalent) with the list of successfully deleted peers (or
document that callers handle notifications if that is the intended contract),
and ensure integratedPeerValidator/accountManager accesses are guarded by nil
checks before use.
| func (s *BaseServer) GRPCServer() *grpc.Server { | ||
| return Create(s, func() *grpc.Server { | ||
| trustedPeers := s.config.ReverseProxy.TrustedPeers | ||
| trustedPeers := s.Config.ReverseProxy.TrustedPeers | ||
| defaultTrustedPeers := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")} | ||
| if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) { | ||
| log.WithContext(context.Background()).Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.") | ||
| trustedPeers = defaultTrustedPeers | ||
| } | ||
| trustedHTTPProxies := s.config.ReverseProxy.TrustedHTTPProxies | ||
| trustedProxiesCount := s.config.ReverseProxy.TrustedHTTPProxiesCount | ||
| trustedHTTPProxies := s.Config.ReverseProxy.TrustedHTTPProxies | ||
| trustedProxiesCount := s.Config.ReverseProxy.TrustedHTTPProxiesCount | ||
| if len(trustedHTTPProxies) > 0 && trustedProxiesCount > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Fix the slices.Equal generic type argument to avoid compilation error
The call:
if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {is invalid because slices.Equal expects the element type (netip.Prefix) as its type argument, and []netip.Prefix is not comparable. You can either rely on type inference or pass the correct element type explicitly:
- if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {
+ if len(trustedPeers) == 0 || slices.Equal(trustedPeers, defaultTrustedPeers) {
+ // or: slices.Equal[netip.Prefix](trustedPeers, defaultTrustedPeers)Without this change the file will not compile.
I'll verify this review comment by checking the Go slices.Equal signature and inspecting the actual code in the repository.
🏁 Script executed:
#!/bin/bash
# Check the actual code at lines 104-114 in the specified file
fd "boot.go" --path "management/internals/server" -x cat {} \; | head -120 | tail -20Length of output: 215
🌐 Web query:
Go slices.Equal generic type parameter signature
💡 Result:
Signature:
func Equal[S ~[]E, E comparable](s1, s2 S) bool. [1][2]
(For a comparator version: func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool.) [1]
I need to verify the actual code in the file. Let me fix the shell script and check if the code exists:
Now let me verify the actual code in the file:
---
🏁 Script executed:
```shell
#!/bin/bash
# Locate and view the boot.go file
find . -name "boot.go" -path "*/management/internals/server/*" -type f -exec cat {} \; | sed -n '100,120p'
Length of output: 1115
Fix the slices.Equal generic type argument to avoid compilation error
The call at line 109 is invalid. The slices.Equal signature is func Equal[S ~[]E, E comparable](s1, s2 S) bool, where the type parameter is E (the element type), not the slice type. Passing []netip.Prefix as the type argument is incorrect; the type argument must be netip.Prefix.
// Incorrect (current):
slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers)
// Correct options:
slices.Equal(trustedPeers, defaultTrustedPeers)
// or:
slices.Equal[netip.Prefix](trustedPeers, defaultTrustedPeers)This code will not compile without the fix.
🤖 Prompt for AI Agents
management/internals/server/boot.go around lines 104-114: the slices.Equal call
incorrectly supplies the slice type as the type parameter which causes a
compilation error; change the call to either slices.Equal(trustedPeers,
defaultTrustedPeers) or slices.Equal[netip.Prefix](trustedPeers,
defaultTrustedPeers) so the type parameter (if provided) is the element type
netip.Prefix rather than []netip.Prefix.
| assert.Equal(t, tc.expectedPeer.Name, got.Name) | ||
| assert.Equal(t, tc.expectedPeer.Meta.WtVersion, got.Version) | ||
| assert.Equal(t, tc.expectedPeer.IP.String(), got.Ip) | ||
| assert.Equal(t, "OS core", got.Os) | ||
| assert.Equal(t, tc.expectedPeer.LoginExpirationEnabled, got.LoginExpirationEnabled) | ||
| assert.Equal(t, tc.expectedPeer.SSHEnabled, got.SshEnabled) | ||
| assert.Equal(t, tc.expectedPeer.Status.Connected, got.Connected) | ||
| assert.Equal(t, tc.expectedPeer.Meta.SystemSerialNumber, got.SerialNumber) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameterize OS expectation and guard against nil got
All other assertions use tc.expectedPeer, but got.Os is checked against a hardcoded "OS core", which is brittle if the peer’s meta changes. Also, got is dereferenced without a nil guard. You can tighten this up as follows:
- t.Log(got)
-
- assert.Equal(t, tc.expectedPeer.Name, got.Name)
- assert.Equal(t, tc.expectedPeer.Meta.WtVersion, got.Version)
- assert.Equal(t, tc.expectedPeer.IP.String(), got.Ip)
- assert.Equal(t, "OS core", got.Os)
+ require.NotNil(t, got)
+ t.Log(got)
+
+ assert.Equal(t, tc.expectedPeer.Name, got.Name)
+ assert.Equal(t, tc.expectedPeer.Meta.WtVersion, got.Version)
+ assert.Equal(t, tc.expectedPeer.IP.String(), got.Ip)
+ expectedOS := fmt.Sprintf("%s %s", tc.expectedPeer.Meta.OS, tc.expectedPeer.Meta.Core)
+ assert.Equal(t, expectedOS, got.Os)
assert.Equal(t, tc.expectedPeer.LoginExpirationEnabled, got.LoginExpirationEnabled)
assert.Equal(t, tc.expectedPeer.SSHEnabled, got.SshEnabled)
assert.Equal(t, tc.expectedPeer.Status.Connected, got.Connected)
assert.Equal(t, tc.expectedPeer.Meta.SystemSerialNumber, got.SerialNumber)This keeps the test aligned with the data it’s constructed from and makes failures easier to interpret.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In management/server/http/handlers/peers/peers_handler_test.go around lines 344
to 351, the test currently asserts got.Os against a hardcoded "OS core" and
dereferences got without checking for nil; change the assertions to first
assert.NotNil(t, got) (or require.NotNil to stop the test), then compare got.Os
to the OS value from the table-driven input (e.g., tc.expectedPeer.Meta.Os or
the correct field on expectedPeer) instead of the literal string so the test
follows the test data and won't panic if got is nil.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
management/internals/controllers/network_map/controller/controller.go (1)
199-244: Goroutine captures outerpeervariable when indexingproxyNetworkMapsInside
sendUpdateAccountPeers, the goroutine correctly takesp *nbpeer.Peeras an argument and usesp.IDalmost everywhere, but it uses the outer loop variablepeerwhen indexingproxyNetworkMaps:for _, peer := range account.Peers { ... go func(p *nbpeer.Peer) { ... proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] // uses outer `peer` if ok { remotePeerNetworkMap.Merge(proxyNetworkMap) } ... }(peer) }Because
peeris reused by theforloop, the goroutines can observepeeras the last value in the slice, so multiple peers may merge the wrong proxy network map.Fix is simple: consistently use
p.IDinside the goroutine.- proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] + proxyNetworkMap, ok := proxyNetworkMaps[p.ID]This keeps the per‑peer proxy data aligned with the peer being processed, and removes a classic closure bug in Go’s
for+ goroutine pattern.
🧹 Nitpick comments (5)
management/internals/controllers/network_map/interface.go (1)
31-38: Interface extension looks consistent; consider normalizingaccountIDnamingThe new lifecycle and batch methods make sense and line up with the controller implementation. One small nit: the parameter names mix
accountIdandaccountIDacross the new methods (e.g.,OnPeersUpdated/DisconnectPeersvsOnPeersAdded/OnPeerConnected/OnPeerDisconnected). For readability and to avoid confusion in call sites, it would be good to standardize on a single form (the file already predominantly usesaccountID).management/internals/controllers/network_map/controller/controller.go (4)
22-47: Ephemeral manager wiring is fine; consider unexported field and nil‑safetyThe introduction of
ephemeral.ManagerintoControllerand wiring viaNewControllerlooks straightforward and keeps concerns localized.Two small follow‑ups to consider:
Exported field
EphemeralPeersManageris the only exported field onController. Unless you explicitly intend to expose it to other packages, making it unexported (e.g.ephemeralPeersManager) would better match the rest of the struct and avoid leaking implementation details.Nil‑safety
NewControlleracceptsephemeralPeersManager ephemeral.Managerand unconditionally stores it. If any caller ever passesnil(e.g. tests or a configuration that doesn’t use ephemeral peers),OnPeerConnected/OnPeerDisconnectedwill panic. Either:
- guarantee and document that
ephemeralPeersManageris never nil, or- guard calls with a nil check.
Example (if you choose to guard):
- EphemeralPeersManager ephemeral.Manager + ephemeralPeersManager ephemeral.Managerand:
- EphemeralPeersManager: ephemeralPeersManager, + ephemeralPeersManager: ephemeralPeersManager,with call‑sites updated accordingly, plus nil checks in the methods (see next comment).
Also applies to: 75-111
715-730: Avoid repeated account lookups in OnPeersAdded for experimental pathFor experimental accounts,
OnPeersAddedfetches the same account on every iteration:for _, peerID := range peerIDs { if c.experimentalNetworkMap(accountID) { account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) if err != nil { return err } err = c.onPeerAddedUpdNetworkMapCache(account, peerID) if err != nil { return err } } } return c.bufferSendUpdateAccountPeers(ctx, accountID)Functionally this is fine, but it does unnecessary repeated work and backpressure calls for the same
accountID.You can fetch the account once and reuse it:
func (c *Controller) OnPeersAdded(ctx context.Context, accountID string, peerIDs []string) error { - for _, peerID := range peerIDs { - if c.experimentalNetworkMap(accountID) { - account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) - if err != nil { - return err - } - - err = c.onPeerAddedUpdNetworkMapCache(account, peerID) - if err != nil { - return err - } - } - } + if c.experimentalNetworkMap(accountID) { + account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) + if err != nil { + return err + } + for _, peerID := range peerIDs { + if err := c.onPeerAddedUpdNetworkMapCache(account, peerID); err != nil { + return err + } + } + } return c.bufferSendUpdateAccountPeers(ctx, accountID) }This keeps behavior the same while avoiding redundant account retrieval.
732-778: OnPeersDeleted: minor perf improvement for experimental path account retrievalThe delete handler looks logically sound: it sends a “clearing”
SyncResponse, closes per‑peer channels, optionally updates the experimental network map cache, then buffers an account‑wide update.For the experimental path you currently fetch the same account for each peer ID:
if c.experimentalNetworkMap(accountID) { account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) if err != nil { log.WithContext(ctx).Errorf("failed to get account %s: %v", accountID, err) continue } err = c.onPeerDeletedUpdNetworkMapCache(account, peerID) if err != nil { log.WithContext(ctx).Errorf("failed to update network map cache for deleted peer %s in account %s: %v", peerID, accountID, err) continue } }If
peerIDsis non‑trivial, this causes repeated identical lookups and potential extra load on the request buffer.You can fetch once and reuse, while preserving per‑peer error logging:
-func (c *Controller) OnPeersDeleted(ctx context.Context, accountID string, peerIDs []string) error { +func (c *Controller) OnPeersDeleted(ctx context.Context, accountID string, peerIDs []string) error { network, err := c.repo.GetAccountNetwork(ctx, accountID) ... - for _, peerID := range peerIDs { + var account *types.Account + if c.experimentalNetworkMap(accountID) { + account, err = c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) + if err != nil { + log.WithContext(ctx).Errorf("failed to get account %s: %v", accountID, err) + // No experimental cache updates, but still proceed with sending delete updates. + account = nil + } + } + for _, peerID := range peerIDs { ... - if c.experimentalNetworkMap(accountID) { - account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID) - if err != nil { - log.WithContext(ctx).Errorf("failed to get account %s: %v", accountID, err) - continue - } - err = c.onPeerDeletedUpdNetworkMapCache(account, peerID) + if account != nil && c.experimentalNetworkMap(accountID) { + err = c.onPeerDeletedUpdNetworkMapCache(account, peerID) if err != nil { log.WithContext(ctx).Errorf("failed to update network map cache for deleted peer %s in account %s: %v", peerID, accountID, err) continue } } }Not critical, but it reduces redundant work on the hot path.
825-827: DisconnectPeers implementation is fine; consider usingaccountIdfor logging
DisconnectPeerscorrectly delegates toCloseChannelsand ignoresaccountId. The implementation is simple and correct.Optionally, you might want to incorporate
accountIdinto tracing logs insidePeersUpdateManager.CloseChannels(or here) to aid debugging, since the interface now threads it through. No change is strictly required.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
management/internals/controllers/network_map/controller/controller.go(6 hunks)management/internals/controllers/network_map/interface.go(1 hunks)management/internals/shared/grpc/server.go(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
management/internals/controllers/network_map/controller/controller.go (4)
management/internals/modules/peers/ephemeral/interface.go (1)
Manager(14-19)management/internals/controllers/network_map/update_channel.go (1)
PeersUpdateManager(5-13)management/server/account/request_buffer.go (1)
RequestBuffer(9-11)management/internals/controllers/network_map/interface.go (2)
Controller(23-39)DnsForwarderPortMinVersion(20-20)
management/internals/controllers/network_map/interface.go (1)
management/internals/controllers/network_map/update_message.go (1)
UpdateMessage(7-9)
management/internals/shared/grpc/server.go (1)
management/internals/shared/grpc/token_mgr.go (1)
SecretsManager(27-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Client / Unit
- GitHub Check: Android / Build
- GitHub Check: Darwin
- GitHub Check: Windows
- GitHub Check: Linux
- GitHub Check: release
- GitHub Check: release_ui
- GitHub Check: Build Cache
- GitHub Check: Client / Unit
- GitHub Check: Check External GPL/AGPL Licenses
- GitHub Check: JS / Lint
🔇 Additional comments (6)
management/internals/shared/grpc/server.go (6)
78-87: LGTM! Constructor signature updated correctly.The
NewServersignature has been properly updated to acceptnetworkMapControllerinstead of the removed ephemeral manager parameters. The parameter ordering is clear and consistent with the struct definition.
93-97: LGTM! Metrics properly routed to network map controller.The connected streams metric now correctly delegates to
networkMapController.CountStreams(), maintaining the same observability while using the new controller abstraction.
339-351: LGTM! Peer disconnect lifecycle properly integrated.The
cancelPeerRoutinesmethod correctly routes peer disconnection throughnetworkMapController.OnPeerDisconnected, maintaining proper cleanup order alongside the account manager and secrets manager.
619-643: LGTM! DNS domain accessed correctly in Login flow.The
GetDNSDomaincall is appropriately used in the one-shot Login response preparation. Since Login doesn't establish a persistent streaming connection (unlike Sync), there's no correspondingOnPeerConnectedcall, which is the correct design.
676-725: LGTM! Initial sync correctly uses network map controller.The
sendInitialSyncmethod properly accessesGetDNSDomainfrom the network map controller (line 705). Note that this occurs beforeOnPeerConnectedis called in the Sync flow (line 264), which appears intentional since the initial sync must be prepared before establishing the updates channel.
264-270: Code handles asymmetric OnPeerConnected/OnPeerDisconnected calls correctly.Verification confirms that when
OnPeerConnectedfails, callingOnPeerDisconnectedviacancelPeerRoutinesis safe:
CloseChannelis idempotent—it gracefully handles closing non-existent channels (logs debug message and returns)EphemeralPeersManager.OnPeerConnectedandOnPeerDisconnectedare both idempotent and check if peers are ephemeral before applying any state changes- Neither method requires prior state from the other; they can be called independently without error
The implementation correctly handles the edge case where
OnPeerConnectedfails before reaching the controller's business logic.
| func (c *Controller) OnPeerConnected(ctx context.Context, accountID string, peerID string) (chan *network_map.UpdateMessage, error) { | ||
| peer, err := c.repo.GetPeerByID(ctx, accountID, peerID) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get peer %s: %v", peerID, err) | ||
| } | ||
|
|
||
| c.EphemeralPeersManager.OnPeerConnected(ctx, peer) | ||
|
|
||
| return c.peersUpdateManager.CreateChannel(ctx, peerID), nil | ||
| } | ||
|
|
||
| func (c *Controller) OnPeerDisconnected(ctx context.Context, accountID string, peerID string) { | ||
| c.peersUpdateManager.CloseChannel(ctx, peerID) | ||
| peer, err := c.repo.GetPeerByID(ctx, accountID, peerID) | ||
| if err != nil { | ||
| log.WithContext(ctx).Errorf("failed to get peer %s: %v", peerID, err) | ||
| return | ||
| } | ||
| c.EphemeralPeersManager.OnPeerDisconnected(ctx, peer) | ||
| } | ||
|
|
||
| func (c *Controller) CountStreams() int { | ||
| return c.peersUpdateManager.CountStreams() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Guard ephemeral manager usage in OnPeerConnected/OnPeerDisconnected
The overall flow of the new lifecycle hooks looks good:
- Lookup peer by
(accountID, peerID). - Notify
EphemeralPeersManagerof connect/disconnect. - Manage per‑peer update channels via
PeersUpdateManager. - Surface a
chan *UpdateMessagefromOnPeerConnected.
Two points to tighten:
- Nil guard for
EphemeralPeersManager
Assuming the field can be nil in any configuration or tests, both methods will panic. A lightweight guard would make this safer:
func (c *Controller) OnPeerConnected(ctx context.Context, accountID string, peerID string) (chan *network_map.UpdateMessage, error) {
peer, err := c.repo.GetPeerByID(ctx, accountID, peerID)
if err != nil {
return nil, fmt.Errorf("failed to get peer %s: %v", peerID, err)
}
- c.EphemeralPeersManager.OnPeerConnected(ctx, peer)
+ if c.EphemeralPeersManager != nil {
+ c.EphemeralPeersManager.OnPeerConnected(ctx, peer)
+ }
return c.peersUpdateManager.CreateChannel(ctx, peerID), nil
}
func (c *Controller) OnPeerDisconnected(ctx context.Context, accountID string, peerID string) {
c.peersUpdateManager.CloseChannel(ctx, peerID)
peer, err := c.repo.GetPeerByID(ctx, accountID, peerID)
if err != nil {
log.WithContext(ctx).Errorf("failed to get peer %s: %v", peerID, err)
return
}
- c.EphemeralPeersManager.OnPeerDisconnected(ctx, peer)
+ if c.EphemeralPeersManager != nil {
+ c.EphemeralPeersManager.OnPeerDisconnected(ctx, peer)
+ }
}- Consistency of error semantics
OnPeerConnected fails fast on repo lookup errors, which is good. OnPeerDisconnected logs and returns on lookup failure, which is also reasonable because the channel is already closed and the peer may legitimately be gone. The behavior is consistent; just be aware that the ephemeral manager may not see a disconnect in that corner case.
I'll verify this review comment by inspecting the codebase to determine if EphemeralPeersManager can actually be nil and whether nil guards are necessary.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the Controller struct definition and EphemeralPeersManager field
fd -e go --exec grep -l "type Controller struct" {} \; | head -5
Length of output: 71
🏁 Script executed:
#!/bin/bash
# Search for EphemeralPeersManager field declaration
rg -n "EphemeralPeersManager\s*\w+" --type go -A 2 -B 2 | head -50Length of output: 543
🏁 Script executed:
#!/bin/bash
# Find Controller initialization functions
rg -n "func.*New.*Controller|func.*newController" --type go -A 10 | head -80Length of output: 7165
🏁 Script executed:
#!/bin/bash
# Search for all usages of EphemeralPeersManager in the codebase
rg -n "EphemeralPeersManager" --type go -C 2 | head -100Length of output: 1985
---
🏁 Script executed:
```shell
#!/bin/bash
# Find ephemeral.Manager type definition
rg -n "type Manager" --type go -A 3 management/internals/ | grep -A 3 "ephemeral"
Length of output: 372
🏁 Script executed:
#!/bin/bash
# Search for all NewController callers
rg -n "NewController\(" --type go -B 2 -A 2Length of output: 26310
🏁 Script executed:
#!/bin/bash
# Check test files for EphemeralPeersManager usage or nil patterns
find . -name "*test.go" -type f -exec rg -l "EphemeralPeersManager\|ephemeralPeersManager" {} \;Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for where ephemeral.Manager is defined
fd -e go -path "*/ephemeral*" --exec grep -l "type Manager" {} \;Length of output: 168
---
🏁 Script executed:
```shell
#!/bin/bash
# Find BaseServer.EphemeralManager() method implementation
rg -n "func.*EphemeralManager" --type go -A 5 | head -40
Length of output: 3585
🏁 Script executed:
#!/bin/bash
# Check if there are any nil checks for EphemeralManager elsewhere in the codebase
rg -n "EphemeralManager.*nil\|nil.*EphemeralManager" --type goLength of output: 0
🏁 Script executed:
#!/bin/bash
# Check the full controller struct definition and field visibility
rg -n "type Controller struct" --type go -A 20 management/internals/controllers/network_map/controller/controller.goLength of output: 798
Add nil guards for EphemeralPeersManager in lifecycle methods
The review comment is valid. While all current code paths initialize EphemeralPeersManager as non-nil, the field is exported and can theoretically become nil if:
- External code bypasses the constructor and sets it directly
- An undocumented edge case in
BaseServer.EphemeralManager()returns nil
Without guards, both OnPeerConnected (line 119) and OnPeerDisconnected (line 131) will panic. Adding nil checks is sound defensive programming for exported interface fields.
The suggested diffs in the review are appropriate:
OnPeerConnectedshould guard before callingc.EphemeralPeersManager.OnPeerConnected()OnPeerDisconnectedshould guard before callingc.EphemeralPeersManager.OnPeerDisconnected()
🤖 Prompt for AI Agents
In management/internals/controllers/network_map/controller/controller.go around
lines 113 to 136, add nil guards for the exported EphemeralPeersManager before
invoking its methods to avoid panics: in OnPeerConnected check if
c.EphemeralPeersManager != nil before calling OnPeerConnected (still return the
peersUpdateManager channel as before if the manager is nil), and in
OnPeerDisconnected check if c.EphemeralPeersManager != nil before calling
OnPeerDisconnected (optionally log a debug/warn when it is nil); leave all other
behavior unchanged.
| func (c *Controller) OnPeersUpdated(ctx context.Context, accountID string, peerIDs []string) error { | ||
| peers, err := c.repo.GetPeersByIDs(ctx, accountID, peerIDs) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get peers by ids: %w", err) | ||
| } | ||
|
|
||
| for _, peer := range peers { | ||
| c.UpdatePeerInNetworkMapCache(accountID, peer) | ||
| } | ||
|
|
||
| err = c.bufferSendUpdateAccountPeers(ctx, accountID) | ||
| if err != nil { | ||
| log.WithContext(ctx).Errorf("failed to buffer update account peers for peer update in account %s: %v", accountID, err) | ||
| } | ||
|
|
||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OnPeersUpdated swallows buffer errors, diverging from other handlers
OnPeersUpdated correctly reloads peers and updates the cache, but it explicitly ignores errors from bufferSendUpdateAccountPeers:
err = c.bufferSendUpdateAccountPeers(ctx, accountID)
if err != nil {
log.WithContext(ctx).Errorf("failed to buffer update account peers for peer update in account %s: %v", accountID, err)
}
return nilBoth OnPeersAdded and OnPeersDeleted propagate bufferSendUpdateAccountPeers’s error to the caller, while OnPeersUpdated hides it. This can lead to silent failures perceived as success by the caller.
Consider aligning behavior with the other methods by returning the error (you can keep logging if you want):
- err = c.bufferSendUpdateAccountPeers(ctx, accountID)
- if err != nil {
- log.WithContext(ctx).Errorf("failed to buffer update account peers for peer update in account %s: %v", accountID, err)
- }
-
- return nil
+ if err := c.bufferSendUpdateAccountPeers(ctx, accountID); err != nil {
+ log.WithContext(ctx).Errorf("failed to buffer update account peers for peer update in account %s: %v", accountID, err)
+ return fmt.Errorf("buffer update account peers for account %s: %w", accountID, err)
+ }
+ return nil🤖 Prompt for AI Agents
In management/internals/controllers/network_map/controller/controller.go around
lines 697 to 713, OnPeersUpdated currently calls bufferSendUpdateAccountPeers
and only logs errors instead of returning them, diverging from
OnPeersAdded/OnPeersDeleted; change the function so that after calling
bufferSendUpdateAccountPeers it returns the error (optionally still logging it)
instead of always returning nil, ensuring the error is propagated to the caller
like the other handlers.




Describe your changes
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.