Skip to content

Conversation

@pascal-fischer
Copy link
Collaborator

@pascal-fischer pascal-fischer commented Nov 14, 2025

Describe your changes

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

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

    • Centralized ephemeral peer handling with a 10-minute lifecycle for temporary peers.
    • Batched peer update/delete operations to reduce noise and improve update efficiency.
  • Chores

    • Internal peer-management refactor for clearer wiring and improved stability.
    • Updated management integrations dependency.

✏️ Tip: You can customize this high-level summary in your review settings.

# 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
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 14, 2025

Walkthrough

The PR moves peer management into a new public peers.Manager, relocates and refactors EphemeralManager to depend on that manager, converts per-peer network-map operations to batch APIs, updates the network-map controller to accept an EphemeralManager and new lifecycle hooks, and simplifies the gRPC server constructor by removing ephemeral/update manager parameters.

Changes

Cohort / File(s) Summary
Peer manager added
management/internals/modules/peers/manager.go, management/internals/modules/peers/manager_mock.go
New public peers.Manager interface and implementation with GetPeer/GetAllPeers/GetPeersByGroupIDs/GetPeerAccountID/DeletePeers and setter methods; corresponding gomock mock updated/added.
Ephemeral manager relocated & refactored
management/internals/modules/peers/ephemeral/interface.go, management/internals/modules/peers/ephemeral/manager/ephemeral.go, management/internals/modules/peers/ephemeral/manager/ephemeral_test.go
EphemeralManager now constructed via NewEphemeralManager(store, peers.Manager) and uses exported ephemeral.EphemeralLifeTime. Cleanup now calls peers.Manager.DeletePeers in batches; tests updated to the new manager mock.
Network-map controller API & implementation
management/internals/controllers/network_map/interface.go, management/internals/controllers/network_map/interface_mock.go, management/internals/controllers/network_map/controller/controller.go, management/internals/controllers/network_map/controller/repository.go
Controller API changed from single-peer methods to batch methods: added OnPeersUpdated/OnPeersAdded/OnPeersDeleted, OnPeerConnected/OnPeerDisconnected, CountStreams(); DisconnectPeers now accepts accountId. Controller gains EphemeralPeersManager field; repository adds GetPeersByIDs and GetPeerByID. Mocks updated.
GRPC server & shared wiring
management/internals/shared/grpc/server.go, client/*, shared/management/client/client_test.go
Removed ephemeralManager and peersUpdateManager parameters/fields from server; switched to using networkMapController methods (OnPeerConnected/OnPeerDisconnected, CountStreams()) for updates and metrics. Tests updated to match new signature.
Server bootstrap / BaseServer changes
management/internals/server/server.go, management/internals/server/boot.go, management/internals/server/controllers.go, management/internals/server/modules.go
BaseServer.config promoted to exported Config; server constructs EphemeralManager via PeersManager() and wires networkMapController with EphemeralManager() and Config. PeersUpdateManager return type adjusted; DNSDomain accessor added.
Batching peer lifecycle in management code
management/server/peer.go, management/server/account.go, management/server/user.go, tests under management/server/*
Replaced per-peer network-map calls with batch calls (OnPeersUpdated/OnPeersAdded/OnPeersDeleted). Added context-aware error wrapping and batching in delete/expire/update flows. Removed some ephemeral setters from account manager. Tests updated accordingly.
HTTP handlers & import paths
management/server/http/handler.go, management/server/http/handlers/peers/peers_handler.go, tests
Swapped peer import path to internals peers manager; removed checkPeerStatus helper and simplified responses; tests adjusted.
Removed old server peer manager
management/server/peers/manager.go
Old peers manager deleted; functionality moved to internals/modules/peers/manager.go.
Test files updated to new wiring
client/cmd/testutil_test.go, client/internal/engine_test.go, client/server/server_test.go, management/server/*_test.go, management/server/http/testing/testing_tools/channel/channel.go, shared/management/client/client_test.go
Many tests updated to construct ephemeral_manager.NewEphemeralManager(store, peers.NewManager(...)) and to remove ephemeral/updateManager args from nbgrpc.NewServer calls; expectations adapted for batch updates.
Go mod update
go.mod
Bumped github.com/netbirdio/management-integrations/integrations version.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus review on:

  • management/internals/modules/peers/manager.go — transactional DeletePeers logic, permission checks, deferred event emission.
  • management/internals/controllers/network_map/controller/controller.go — channel lifecycle (OnPeerConnected/OnPeerDisconnected) and EphemeralManager integration.
  • management/internals/modules/peers/ephemeral/manager/ephemeral.go — batched deletion integration with peers.Manager and timing behavior.
  • management/internals/shared/grpc/server.go — correctness of replacing update channels with controller calls and metrics via CountStreams().
  • Widespread signature changes — ensure all call sites and tests match new function prototypes.

Possibly related PRs

Suggested reviewers

  • crn4
  • mlsmaycon

Poem

🐇 Hopping through commits so spry,

Ephemeral timers wink an eye,
Peers gather in tidy bands,
Controllers clap their clever hands,
Batched updates dance — code leaps high.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is mostly empty with only template placeholders filled. It lacks any explanation of the changes, motivation, issue reference, or detailed rationale for the refactoring. Add a detailed description of the refactoring changes, explain the motivation, reference any related issues, and clarify why tests were not added despite the substantial API changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: refactoring the network map controller. It is specific and directly relates to the substantial refactoring evident in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/network-map-controller

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

pascal-fischer and others added 11 commits November 14, 2025 15:29
# 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
@pascal-fischer pascal-fischer marked this pull request as ready for review November 20, 2025 16:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 recorder

The handler writes to recorder, but the status check uses rr (Line 307). That means this test will keep passing even if the handler returns the wrong status code. Also, the error message always prints http.StatusOK instead of tc.expectedStatus. You can simplify by removing rr and asserting on res.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 peers

In 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 for peerShouldReceiveUpdate(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 invalid for range over integer constant in batching test

In TestCleanupSchedulingBehaviorIsBatched, ephemeralPeers is declared as an integer constant, but the loop uses for 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:

  1. Similar to line 76, the removal of checkPeerStatus means the peer's Connected status is used directly. Ensure this is accurately maintained elsewhere.

  2. The accessiblePeersCount parameter is hardcoded to 0 for 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 shared peers.Manager (and validator) instance for closer prod parity

You’re constructing two separate peers.Manager instances backed by the same store/permissions: one inline for NewEphemeralManager and another as peersManager for NewAPIHandler. Functionally this works, but reusing a single peersManager for both wiring points would more closely mirror production and avoid redundant instances. Similarly, you already build validatorMock above; passing that into NewController instead of a fresh server.MockIntegratedValidator{} literal would keep the mocks consistent.

Also applies to: 76-97

management/internals/server/server.go (1)

43-48: Exported Config field works but relies on non‑nil invariant

Switching from a private config to exported Config and updating call sites preserves existing behavior, but Start now publicly relies on Config and Config.HttpConfig being non‑nil (dereferenced without checks). Since this struct is part of the public surface, consider either:

  • Clearly documenting that Config and Config.HttpConfig must be non‑nil before calling Start, 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.Config for geolocation, account, and IDP managers is consistent with BaseServer’s public Config field and removes the old s.config dependency.
  • The new PeersManager constructor plus AfterInit wiring of NetworkMapController, IntegratedValidator, and AccountManager cleanly centralizes peer-related dependencies and matches the new peers module design.

No obvious issues as long as AfterInit runs 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 imports

Using network_map.NewMockController with a gomock.NewController(t) and asserting OnPeersDeleted(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 use github.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 of peers.Manager and EphemeralManager into controller/server looks consistent

The new peersManager := peers.NewManager(store, permissionsManager) and manager.NewEphemeralManager(store, peersManager) usage in startManagement matches the updated controller.NewController and nbgrpc.NewServer signatures and keeps concerns localized to the test helper. Only optional nit: you now have both ia and server.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 normalizing accountID naming

The extended Controller interface (batch peer operations, CountStreams, OnPeerConnected/OnPeerDisconnected) is coherent with the new responsibilities and matches the mock signatures.

Minor nit: parameter names mix accountId and accountID (e.g., OnPeersUpdated and DisconnectPeers vs OnPeersAdded/OnPeersDeleted). For consistency with existing code and common Go style, you might want to standardize on accountID across these methods.

management/server/management_proto_test.go (1)

27-29: Consider stopping EphemeralManager in test cleanup to avoid leaked goroutines

ephemeralMgr is created and passed into controller.NewController, but never stopped. If the manager starts background workers, they may outlive the test and make debugging harder.

You can wrap the existing cleanup function 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

expireAndUpdatePeers always calls OnPeersUpdated even if peerIDs is empty. This adds unnecessary DB work and assumes GetPeersByIDs tolerates 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

deleteRegularUser returns a bool that’s now ignored by both DeleteUser and DeleteRegularUsers, so the extra return value adds noise without behavior.

Consider changing it to return only error and 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 nil

And 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/OnPeersDeleted run even when peerIDs is empty; adding an early if len(peerIDs) == 0 { return nil } avoids unnecessary store and buffer work.
  • In OnPeersAdded and the experimental branch of OnPeersDeleted, you call GetAccountWithBackpressure once 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

📥 Commits

Reviewing files that changed from the base of the PR and between 68f56b7 and 0d7aa44.

⛔ Files ignored due to path filters (1)
  • go.sum is 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/peers exist in the codebase. The new path github.com/netbirdio/netbird/management/internals/modules/peers is 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 checkPeerStatus function 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 (via am.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:

  1. Version format is valid: The pseudo-version v0.0.0-20251114143509-4eff2374da63 follows Go's standard format (base version + timestamp + commit hash).

  2. Dependencies are consistent: The version is present in both go.mod and go.sum with a valid hash, indicating successful resolution.

  3. Code integration is successful: EphemeralManager is properly wired with peers.Manager across 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.

  4. 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 good

Exporting EphemeralLifeTime = 10 * time.Minute here 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 via peers.Manager looks sound; verify delete semantics

The move to inject peers.Manager and use ephemeral.EphemeralLifeTime plus per‑account batching in cleanup is clean and improves cohesion (all deletions now go through the standard peers path). Locking and timer rescheduling still look correct: deletions are computed under peersLock, then the lock is released before calling DeletePeers.

The only behavioral knobs worth re‑checking against the previous implementation are:

  • Using activity.SystemInitiator as the initiator for these deletions.
  • Passing checkConnected = true to DeletePeers, 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 and BuildManager looks consistent

Injecting EphemeralManager into controller.NewController and passing a concrete &config.Config{} into BuildManager align 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 batch OnPeersUpdated with proper error propagation

Using 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 EphemeralManager

Passing ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)) into controller.NewController updates 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

startServer now builds networkMapController with ephemeral_manager.NewEphemeralManager(str, peers.NewManager(str, permissionsManager)), matching the new NewController signature, and the nbgrpc.NewServer call 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 into NewController looks consistent

The test now constructs ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)) and passes it into controller.NewController, matching the new constructor signature and using the same store and permissionsManager as 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 matches BaseServer.Config

The refactor from s.config to s.Config in Store() and EventStore() is consistent with BaseServer holding a *nbconfig.Config. Using s.Config.StoreConfig.Engine, s.Config.Datadir, and s.Config.DataStoreEncryptionKey keeps 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 signatures

Using s.Config.HttpConfig and s.Config.Datadir for 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.NewServer signature and centralizes GRPC behavior around the same *nbconfig.Config instance used elsewhere in BaseServer.

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 the network_map.PeersUpdateManager interface backed by update_channel.NewPeersUpdateManager(s.Metrics()), matching the interface definition.
  • SecretsManager() correctly passes that PeersUpdateManager along with s.Config.TURNConfig and s.Config.Relay into grpc.NewTimeBasedAuthSecretsManager, keeping TURN/relay token refresh tied to the same update pipeline.
  • AuthManager() now sources all auth-related values from s.Config.HttpConfig and s.Config.GetAuthAudiences(), which matches the config layout.
  • EphemeralManager() is now built from manager.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 NewController and wires in the new EphemeralManager and DNS domain accessor cleanly. The added DNSDomain() method simply exposes s.dnsDomain and keeps controller construction consistent with the base server’s configuration.

management/internals/shared/grpc/server.go (3)

53-75: Constructor and metrics now depend on networkMapController as the single source of stream state

Server now holds config, secretsManager, appMetrics, peerLocks, authManager, and networkMapController, and NewServer wires 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 to networkMapController and secrets manager

  • After initial sync, the server now calls updates := s.networkMapController.OnPeerConnected(ctx, accountID, peer.ID) and passes that channel to handleUpdates, so all subsequent network map updates flow through the controller.
  • handleUpdates operates on chan *network_map.UpdateMessage, closes cleanly when the channel is closed or when srv.Context() is done, and calls cancelPeerRoutines in both cases.
  • cancelPeerRoutines retains the existing accountManager.OnPeerDisconnected call and adds s.networkMapController.OnPeerDisconnected(ctx, accountID, peer.ID) plus s.secretsManager.CancelRefresh(peer.ID). Together with s.secretsManager.SetupRefresh(ctx, accountID, peer.ID) in Sync, 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/OnPeerDisconnected and 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

  • prepareLoginResponse now uses s.networkMapController.GetDNSDomain(settings) and s.config.HttpConfig when building NetbirdConfig and PeerConfig, making DNS domain resolution the controller’s responsibility while still sourcing other fields from s.config.
  • sendInitialSync similarly passes s.config and s.config.HttpConfig into ToSyncResponse, along with s.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.DeviceAuthorizationFlow and s.config.PKCEAuthorizationFlow respectively, 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.Config for all other management-plane configuration.

Also applies to: 669-719, 721-777, 779-833

management/server/peer.go (3)

103-146: Batch OnPeersUpdated usage in MarkPeerConnected and UpdatePeer improves consistency and error handling

  • MarkPeerConnected now calls am.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.
  • UpdatePeer similarly switches to OnPeersUpdated with 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 accountID and peer.ID and use contextual error messages, which will make diagnosing controller-side issues easier.

Also applies to: 185-321


323-379: Deletion and addition now use OnPeersDeleted/OnPeersAdded batch APIs

  • DeletePeer calls OnPeersDeleted(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.
  • AddPeer calls OnPeersAdded(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 calls GetValidatedPeerWithMap as 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 on OnPeersUpdated with proper error propagation

  • In SyncPeer, whenever status changes, sync.UpdateAccountPeers is true, or metadata/posture versioning implies a net map change, the code now calls OnPeersUpdated(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 calls OnPeersUpdated and 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 interface

The added imports and methods (DeletePeers, SetAccountManager, SetIntegratedPeerValidator, SetNetworkMapController and their recorders) correctly mirror the Manager interface 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 interface

The newly added mocks (CountStreams, updated DisconnectPeers, and the various OnPeer*/OnPeers* methods) and their recorders are consistent with the updated Controller interface 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 consistent

The added EphemeralPeersManager field, constructor parameter, and OnPeerConnected/OnPeerDisconnected hooks integrate ephemeral handling cleanly with stream creation/teardown and keep peersUpdateManager responsibilities focused.

Also applies to: 75-111, 113-137

Comment on lines 96 to 99
store := &MockStore{}
am := MockAccountManager{
store: store,
}
ctrl := gomock.NewController(t)
peersManager := peers.NewMockManager(ctrl)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +34 to +61
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
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 DeletePeers are responsible for notifying the network map controller themselves, or
  • This method should also trigger a network‑map refresh (e.g., collect successfully deleted peerIDs and call networkMapController.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.go

Length 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.go

Length 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 2

Length of output: 5685


Add nil guards to DeletePeers and clarify network-map notification strategy

Verification confirms the review concerns are valid:

  1. Unguarded nil dereferences: DeletePeers directly accesses m.networkMapController (line 101), m.integratedPeerValidator (line 119), and m.accountManager (lines 139, 156) without checking if they are nil. If any Set* method is omitted during initialization, DeletePeers will panic.

  2. Network-map notification gap: DeletePeers calls m.networkMapController.GetDNSDomain(settings) at line 101 but never notifies it of peer deletions. It only stores events via accountManager.StoreEvent. Confirm whether the network-map controller should be notified of deletions (e.g., call OnPeersDeleted for 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.

Comment on lines 104 to 114
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -20

Length 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.

Comment on lines +344 to +351
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue
1 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 outer peer variable when indexing proxyNetworkMaps

Inside sendUpdateAccountPeers, the goroutine correctly takes p *nbpeer.Peer as an argument and uses p.ID almost everywhere, but it uses the outer loop variable peer when indexing proxyNetworkMaps:

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 peer is reused by the for loop, the goroutines can observe peer as the last value in the slice, so multiple peers may merge the wrong proxy network map.

Fix is simple: consistently use p.ID inside 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 normalizing accountID naming

The new lifecycle and batch methods make sense and line up with the controller implementation. One small nit: the parameter names mix accountId and accountID across the new methods (e.g., OnPeersUpdated/DisconnectPeers vs OnPeersAdded/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 uses accountID).

management/internals/controllers/network_map/controller/controller.go (4)

22-47: Ephemeral manager wiring is fine; consider unexported field and nil‑safety

The introduction of ephemeral.Manager into Controller and wiring via NewController looks straightforward and keeps concerns localized.

Two small follow‑ups to consider:

  1. Exported field
    EphemeralPeersManager is the only exported field on Controller. 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.

  2. Nil‑safety
    NewController accepts ephemeralPeersManager ephemeral.Manager and unconditionally stores it. If any caller ever passes nil (e.g. tests or a configuration that doesn’t use ephemeral peers), OnPeerConnected/OnPeerDisconnected will panic. Either:

    • guarantee and document that ephemeralPeersManager is never nil, or
    • guard calls with a nil check.

Example (if you choose to guard):

-	EphemeralPeersManager ephemeral.Manager
+	ephemeralPeersManager ephemeral.Manager

and:

-		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 path

For experimental accounts, OnPeersAdded fetches 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 retrieval

The 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 peerIDs is 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 using accountId for logging

DisconnectPeers correctly delegates to CloseChannels and ignores accountId. The implementation is simple and correct.

Optionally, you might want to incorporate accountId into tracing logs inside PeersUpdateManager.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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7aa44 and b5d3a1e.

📒 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 NewServer signature has been properly updated to accept networkMapController instead 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 cancelPeerRoutines method correctly routes peer disconnection through networkMapController.OnPeerDisconnected, maintaining proper cleanup order alongside the account manager and secrets manager.


619-643: LGTM! DNS domain accessed correctly in Login flow.

The GetDNSDomain call is appropriately used in the one-shot Login response preparation. Since Login doesn't establish a persistent streaming connection (unlike Sync), there's no corresponding OnPeerConnected call, which is the correct design.


676-725: LGTM! Initial sync correctly uses network map controller.

The sendInitialSync method properly accesses GetDNSDomain from the network map controller (line 705). Note that this occurs before OnPeerConnected is 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 OnPeerConnected fails, calling OnPeerDisconnected via cancelPeerRoutines is safe:

  • CloseChannel is idempotent—it gracefully handles closing non-existent channels (logs debug message and returns)
  • EphemeralPeersManager.OnPeerConnected and OnPeerDisconnected are 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 OnPeerConnected fails before reaching the controller's business logic.

Comment on lines +113 to +136
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()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 EphemeralPeersManager of connect/disconnect.
  • Manage per‑peer update channels via PeersUpdateManager.
  • Surface a chan *UpdateMessage from OnPeerConnected.

Two points to tighten:

  1. 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)
+	}
 }
  1. 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 -50

Length of output: 543


🏁 Script executed:

#!/bin/bash
# Find Controller initialization functions
rg -n "func.*New.*Controller|func.*newController" --type go -A 10 | head -80

Length of output: 7165


🏁 Script executed:

#!/bin/bash
# Search for all usages of EphemeralPeersManager in the codebase
rg -n "EphemeralPeersManager" --type go -C 2 | head -100

Length 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 2

Length 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 go

Length 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.go

Length 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:

  • OnPeerConnected should guard before calling c.EphemeralPeersManager.OnPeerConnected()
  • OnPeerDisconnected should guard before calling c.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.

Comment on lines +697 to 713
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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 nil

Both 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants