diff --git a/management/cmd/management.go b/management/cmd/management.go index 5301485cb0b..4f34009b7e1 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -276,10 +276,10 @@ var ( userManager := users.NewManager(store) settingsManager := settings.NewManager(store) permissionsManager := permissions.NewManager(userManager, settingsManager) - groupsManager := groups.NewManager(store, permissionsManager) + groupsManager := groups.NewManager(store, permissionsManager, accountManager) resourcesManager := resources.NewManager(store, permissionsManager, groupsManager, accountManager) routersManager := routers.NewManager(store, permissionsManager, accountManager) - networksManager := networks.NewManager(store, permissionsManager, resourcesManager) + networksManager := networks.NewManager(store, permissionsManager, resourcesManager, routersManager, accountManager) httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, networksManager, resourcesManager, routersManager, groupsManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator) if err != nil { diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 2165eba9c44..5379a8dd81b 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -154,6 +154,21 @@ const ( AccountRoutingPeerDNSResolutionEnabled Activity = 71 AccountRoutingPeerDNSResolutionDisabled Activity = 72 + + NetworkCreated Activity = 73 + NetworkUpdated Activity = 74 + NetworkDeleted Activity = 75 + + NetworkResourceCreated Activity = 76 + NetworkResourceUpdated Activity = 77 + NetworkResourceDeleted Activity = 78 + + NetworkRouterCreated Activity = 79 + NetworkRouterUpdated Activity = 80 + NetworkRouterDeleted Activity = 81 + + ResourceAddedToGroup Activity = 82 + ResourceRemovedFromGroup Activity = 83 ) var activityMap = map[Activity]Code{ @@ -234,6 +249,21 @@ var activityMap = map[Activity]Code{ AccountRoutingPeerDNSResolutionEnabled: {"Account routing peer DNS resolution enabled", "account.setting.routing.peer.dns.resolution.enable"}, AccountRoutingPeerDNSResolutionDisabled: {"Account routing peer DNS resolution disabled", "account.setting.routing.peer.dns.resolution.disable"}, + + NetworkCreated: {"Network created", "network.create"}, + NetworkUpdated: {"Network updated", "network.update"}, + NetworkDeleted: {"Network deleted", "network.delete"}, + + NetworkResourceCreated: {"Network resource created", "network.resource.create"}, + NetworkResourceUpdated: {"Network resource updated", "network.resource.update"}, + NetworkResourceDeleted: {"Network resource deleted", "network.resource.delete"}, + + NetworkRouterCreated: {"Network router created", "network.router.create"}, + NetworkRouterUpdated: {"Network router updated", "network.router.update"}, + NetworkRouterDeleted: {"Network router deleted", "network.router.delete"}, + + ResourceAddedToGroup: {"Resource added to group", "resource.group.add"}, + ResourceRemovedFromGroup: {"Resource removed from group", "resource.group.delete"}, } // StringCode returns a string code of the activity diff --git a/management/server/groups/manager.go b/management/server/groups/manager.go index b72faf4a260..1162348bd07 100644 --- a/management/server/groups/manager.go +++ b/management/server/groups/manager.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + s "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/store" @@ -14,22 +16,24 @@ type Manager interface { GetAllGroups(ctx context.Context, accountID, userID string) (map[string]*types.Group, error) GetResourceGroupsInTransaction(ctx context.Context, transaction store.Store, lockingStrength store.LockingStrength, accountID, resourceID string) ([]*types.Group, error) AddResourceToGroup(ctx context.Context, accountID, userID, groupID string, resourceID *types.Resource) error - AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resourceID *types.Resource) error - RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) error + AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resourceID *types.Resource) (func(), error) + RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) (func(), error) } type managerImpl struct { store store.Store permissionsManager permissions.Manager + accountManager s.AccountManager } type mockManager struct { } -func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { +func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager s.AccountManager) Manager { return &managerImpl{ store: store, permissionsManager: permissionsManager, + accountManager: accountManager, } } @@ -64,15 +68,40 @@ func (m *managerImpl) AddResourceToGroup(ctx context.Context, accountID, userID, return err } - return m.AddResourceToGroupInTransaction(ctx, m.store, accountID, groupID, resource) + event, err := m.AddResourceToGroupInTransaction(ctx, m.store, accountID, groupID, resource) + if err != nil { + return fmt.Errorf("error adding resource to group: %w", err) + } + + event() + + return nil } -func (m *managerImpl) AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resource *types.Resource) error { - return transaction.AddResourceToGroup(ctx, accountID, groupID, resource) +func (m *managerImpl) AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resource *types.Resource) (func(), error) { + err := transaction.AddResourceToGroup(ctx, accountID, groupID, resource) + if err != nil { + return nil, fmt.Errorf("error adding resource to group: %w", err) + } + + event := func() { + m.accountManager.StoreEvent(ctx, accountID, groupID, accountID, activity.ResourceAddedToGroup, nil) + } + + return event, nil } -func (m *managerImpl) RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) error { - return transaction.RemoveResourceFromGroup(ctx, accountID, groupID, resourceID) +func (m *managerImpl) RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) (func(), error) { + err := transaction.RemoveResourceFromGroup(ctx, accountID, groupID, resourceID) + if err != nil { + return nil, fmt.Errorf("error removing resource from group: %w", err) + } + + event := func() { + m.accountManager.StoreEvent(ctx, accountID, groupID, accountID, activity.ResourceRemovedFromGroup, nil) + } + + return event, nil } func (m *managerImpl) GetResourceGroupsInTransaction(ctx context.Context, transaction store.Store, lockingStrength store.LockingStrength, accountID, resourceID string) ([]*types.Group, error) { @@ -128,12 +157,16 @@ func (m *mockManager) AddResourceToGroup(ctx context.Context, accountID, userID, return nil } -func (m *mockManager) AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resourceID *types.Resource) error { - return nil +func (m *mockManager) AddResourceToGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID string, resourceID *types.Resource) (func(), error) { + return func() { + // noop + }, nil } -func (m *mockManager) RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) error { - return nil +func (m *mockManager) RemoveResourceFromGroupInTransaction(ctx context.Context, transaction store.Store, accountID, groupID, resourceID string) (func(), error) { + return func() { + // noop + }, nil } func NewManagerMock() Manager { diff --git a/management/server/networks/manager.go b/management/server/networks/manager.go index d5291d9dafb..cc7b546a803 100644 --- a/management/server/networks/manager.go +++ b/management/server/networks/manager.go @@ -6,7 +6,10 @@ import ( "github.com/rs/xid" + s "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/networks/resources" + "github.com/netbirdio/netbird/management/server/networks/routers" "github.com/netbirdio/netbird/management/server/networks/types" "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/status" @@ -23,15 +26,19 @@ type Manager interface { type managerImpl struct { store store.Store + accountManager s.AccountManager permissionsManager permissions.Manager resourcesManager resources.Manager + routersManager routers.Manager } -func NewManager(store store.Store, permissionsManager permissions.Manager, manager resources.Manager) Manager { +func NewManager(store store.Store, permissionsManager permissions.Manager, resourceManager resources.Manager, routersManager routers.Manager, accountManager s.AccountManager) Manager { return &managerImpl{ store: store, permissionsManager: permissionsManager, - resourcesManager: manager, + resourcesManager: resourceManager, + routersManager: routersManager, + accountManager: accountManager, } } @@ -58,7 +65,14 @@ func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network network.ID = xid.New().String() - return network, m.store.SaveNetwork(ctx, store.LockingStrengthUpdate, network) + err = m.store.SaveNetwork(ctx, store.LockingStrengthUpdate, network) + if err != nil { + return nil, fmt.Errorf("failed to save network: %w", err) + } + + m.accountManager.StoreEvent(ctx, userID, network.ID, network.AccountID, activity.NetworkCreated, network.EventMeta()) + + return network, nil } func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) { @@ -82,6 +96,13 @@ func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network return nil, status.NewPermissionDeniedError() } + _, err = m.store.GetNetworkByID(ctx, store.LockingStrengthUpdate, network.AccountID, network.ID) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + + m.accountManager.StoreEvent(ctx, userID, network.ID, network.AccountID, activity.NetworkUpdated, network.EventMeta()) + return network, m.store.SaveNetwork(ctx, store.LockingStrengthUpdate, network) } @@ -94,20 +115,24 @@ func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, netw return status.NewPermissionDeniedError() } - unlock := m.store.AcquireWriteLockByUID(ctx, accountID) - defer unlock() + network, err := m.store.GetNetworkByID(ctx, store.LockingStrengthUpdate, accountID, networkID) + if err != nil { + return fmt.Errorf("failed to get network: %w", err) + } - return m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + var eventsToStore []func() + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { resources, err := transaction.GetNetworkResourcesByNetID(ctx, store.LockingStrengthUpdate, accountID, networkID) if err != nil { return fmt.Errorf("failed to get resources in network: %w", err) } for _, resource := range resources { - err = m.resourcesManager.DeleteResourceInTransaction(ctx, transaction, accountID, networkID, resource.ID) + event, err := m.resourcesManager.DeleteResourceInTransaction(ctx, transaction, accountID, networkID, resource.ID) if err != nil { return fmt.Errorf("failed to delete resource: %w", err) } + eventsToStore = append(eventsToStore, event...) } routers, err := transaction.GetNetworkRoutersByNetID(ctx, store.LockingStrengthUpdate, accountID, networkID) @@ -116,12 +141,33 @@ func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, netw } for _, router := range routers { - err = transaction.DeleteNetworkRouter(ctx, store.LockingStrengthUpdate, accountID, router.ID) + event, err := m.routersManager.DeleteRouterInTransaction(ctx, transaction, accountID, networkID, router.ID) if err != nil { return fmt.Errorf("failed to delete router: %w", err) } + eventsToStore = append(eventsToStore, event) } - return transaction.DeleteNetwork(ctx, store.LockingStrengthUpdate, accountID, networkID) + err = transaction.DeleteNetwork(ctx, store.LockingStrengthUpdate, accountID, networkID) + if err != nil { + return fmt.Errorf("failed to delete network: %w", err) + } + + eventsToStore = append(eventsToStore, func() { + m.accountManager.StoreEvent(ctx, userID, networkID, accountID, activity.NetworkDeleted, network.EventMeta()) + }) + + return nil }) + if err != nil { + return fmt.Errorf("failed to delete network: %w", err) + } + + for _, event := range eventsToStore { + event() + } + + go m.accountManager.UpdateAccountPeers(ctx, accountID) + + return nil } diff --git a/management/server/networks/manager_test.go b/management/server/networks/manager_test.go index 0c0c5cad960..edd830c2564 100644 --- a/management/server/networks/manager_test.go +++ b/management/server/networks/manager_test.go @@ -9,6 +9,7 @@ import ( "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/mock_server" "github.com/netbirdio/netbird/management/server/networks/resources" + "github.com/netbirdio/netbird/management/server/networks/routers" "github.com/netbirdio/netbird/management/server/networks/types" "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/store" @@ -27,8 +28,9 @@ func Test_GetAllNetworksReturnsNetworks(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) networks, err := manager.GetAllNetworks(ctx, accountID, userID) require.NoError(t, err) @@ -49,8 +51,9 @@ func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) networks, err := manager.GetAllNetworks(ctx, accountID, userID) require.Error(t, err) @@ -71,8 +74,9 @@ func Test_GetNetworkReturnsNetwork(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) networks, err := manager.GetNetwork(ctx, accountID, userID, networkID) require.NoError(t, err) @@ -93,8 +97,9 @@ func Test_GetNetworkReturnsPermissionDenied(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) network, err := manager.GetNetwork(ctx, accountID, userID, networkID) require.Error(t, err) @@ -117,8 +122,9 @@ func Test_CreateNetworkSuccessfully(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) createdNetwork, err := manager.CreateNetwork(ctx, userID, network) require.NoError(t, err) @@ -141,8 +147,9 @@ func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) createdNetwork, err := manager.CreateNetwork(ctx, userID, network) require.Error(t, err) @@ -163,8 +170,9 @@ func Test_DeleteNetworkSuccessfully(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) err = manager.DeleteNetwork(ctx, accountID, userID, networkID) require.NoError(t, err) @@ -184,8 +192,9 @@ func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) err = manager.DeleteNetwork(ctx, accountID, userID, networkID) require.Error(t, err) @@ -208,8 +217,9 @@ func Test_UpdateNetworkSuccessfully(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) updatedNetwork, err := manager.UpdateNetwork(ctx, userID, network) require.NoError(t, err) @@ -234,8 +244,9 @@ func Test_UpdateNetworkFailsWithPermissionDenied(t *testing.T) { am := mock_server.MockAccountManager{} permissionsManager := permissions.NewManagerMock() groupsManager := groups.NewManagerMock() + routerManager := routers.NewManagerMock() resourcesManager := resources.NewManager(s, permissionsManager, groupsManager, &am) - manager := NewManager(s, permissionsManager, resourcesManager) + manager := NewManager(s, permissionsManager, resourcesManager, routerManager, &am) updatedNetwork, err := manager.UpdateNetwork(ctx, userID, network) require.Error(t, err) diff --git a/management/server/networks/resources/manager.go b/management/server/networks/resources/manager.go index 30222ae75f8..bc27d6c2fbf 100644 --- a/management/server/networks/resources/manager.go +++ b/management/server/networks/resources/manager.go @@ -6,6 +6,7 @@ import ( "fmt" s "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/networks/resources/types" "github.com/netbirdio/netbird/management/server/permissions" @@ -23,7 +24,7 @@ type Manager interface { GetResource(ctx context.Context, accountID, userID, networkID, resourceID string) (*types.NetworkResource, error) UpdateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error - DeleteResourceInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, resourceID string) error + DeleteResourceInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, resourceID string) ([]func(), error) } type managerImpl struct { @@ -102,26 +103,38 @@ func (m *managerImpl) CreateResource(ctx context.Context, userID string, resourc return nil, fmt.Errorf("failed to create new network resource: %w", err) } + var eventsToStore []func() err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { _, err = transaction.GetNetworkResourceByName(ctx, store.LockingStrengthShare, resource.AccountID, resource.Name) if err == nil { return errors.New("resource already exists") } + network, err := transaction.GetNetworkByID(ctx, store.LockingStrengthUpdate, resource.AccountID, resource.NetworkID) + if err != nil { + return fmt.Errorf("failed to get network: %w", err) + } + err = transaction.SaveNetworkResource(ctx, store.LockingStrengthUpdate, resource) if err != nil { return fmt.Errorf("failed to save network resource: %w", err) } + event := func() { + m.accountManager.StoreEvent(ctx, userID, resource.ID, resource.AccountID, activity.NetworkResourceCreated, resource.EventMeta(network.Name)) + } + eventsToStore = append(eventsToStore, event) + res := nbtypes.Resource{ ID: resource.ID, Type: resource.Type.String(), } for _, groupID := range resource.GroupIDs { - err = m.groupsManager.AddResourceToGroupInTransaction(ctx, transaction, resource.AccountID, groupID, &res) + event, err := m.groupsManager.AddResourceToGroupInTransaction(ctx, transaction, resource.AccountID, groupID, &res) if err != nil { return fmt.Errorf("failed to add resource to group: %w", err) } + eventsToStore = append(eventsToStore, event) } return nil @@ -130,6 +143,10 @@ func (m *managerImpl) CreateResource(ctx context.Context, userID string, resourc return nil, fmt.Errorf("failed to create network resource: %w", err) } + for _, event := range eventsToStore { + event() + } + go m.accountManager.UpdateAccountPeers(ctx, resource.AccountID) return resource, nil @@ -174,7 +191,17 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc resource.Domain = domain resource.Prefix = prefix + var eventsToStore []func() err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + network, err := transaction.GetNetworkByID(ctx, store.LockingStrengthUpdate, resource.AccountID, resource.NetworkID) + if err != nil { + return fmt.Errorf("failed to get network: %w", err) + } + + if network.ID != resource.NetworkID { + return status.NewResourceNotPartOfNetworkError(resource.ID, resource.NetworkID) + } + _, err = transaction.GetNetworkResourceByID(ctx, store.LockingStrengthShare, resource.AccountID, resource.ID) if err != nil { return fmt.Errorf("failed to get network resource: %w", err) @@ -195,8 +222,19 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc return fmt.Errorf("failed to save network resource: %w", err) } - return m.updateResourceGroups(ctx, transaction, resource, oldResource) + events, err := m.updateResourceGroups(ctx, transaction, resource, oldResource) + if err != nil { + return fmt.Errorf("failed to update resource groups: %w", err) + } + + eventsToStore = append(eventsToStore, events...) + eventsToStore = append(eventsToStore, func() { + m.accountManager.StoreEvent(ctx, userID, resource.ID, resource.AccountID, activity.NetworkResourceUpdated, resource.EventMeta(network.Name)) + }) + + return nil }) + if err != nil { return nil, fmt.Errorf("failed to update network resource: %w", err) } @@ -206,7 +244,7 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc return resource, nil } -func (m *managerImpl) updateResourceGroups(ctx context.Context, transaction store.Store, newResource, oldResource *types.NetworkResource) error { +func (m *managerImpl) updateResourceGroups(ctx context.Context, transaction store.Store, newResource, oldResource *types.NetworkResource) ([]func(), error) { res := nbtypes.Resource{ ID: newResource.ID, Type: newResource.Type.String(), @@ -214,7 +252,7 @@ func (m *managerImpl) updateResourceGroups(ctx context.Context, transaction stor oldResourceGroups, err := m.groupsManager.GetResourceGroupsInTransaction(ctx, transaction, store.LockingStrengthUpdate, oldResource.AccountID, oldResource.ID) if err != nil { - return fmt.Errorf("failed to get resource groups: %w", err) + return nil, fmt.Errorf("failed to get resource groups: %w", err) } oldGroupsIds := make([]string, 0) @@ -222,23 +260,26 @@ func (m *managerImpl) updateResourceGroups(ctx context.Context, transaction stor oldGroupsIds = append(oldGroupsIds, group.ID) } + var eventsToStore []func() groupsToAdd := util.Difference(newResource.GroupIDs, oldGroupsIds) for _, groupID := range groupsToAdd { - err = m.groupsManager.AddResourceToGroupInTransaction(ctx, transaction, newResource.AccountID, groupID, &res) + events, err := m.groupsManager.AddResourceToGroupInTransaction(ctx, transaction, newResource.AccountID, groupID, &res) if err != nil { - return fmt.Errorf("failed to add resource to group: %w", err) + return nil, fmt.Errorf("failed to add resource to group: %w", err) } + eventsToStore = append(eventsToStore, events) } groupsToRemove := util.Difference(oldGroupsIds, newResource.GroupIDs) for _, groupID := range groupsToRemove { - err = m.groupsManager.RemoveResourceFromGroupInTransaction(ctx, transaction, newResource.AccountID, groupID, res.ID) + events, err := m.groupsManager.RemoveResourceFromGroupInTransaction(ctx, transaction, newResource.AccountID, groupID, res.ID) if err != nil { - return fmt.Errorf("failed to add resource to group: %w", err) + return nil, fmt.Errorf("failed to add resource to group: %w", err) } + eventsToStore = append(eventsToStore, events) } - return nil + return eventsToStore, nil } func (m *managerImpl) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error { @@ -253,44 +294,68 @@ func (m *managerImpl) DeleteResource(ctx context.Context, accountID, userID, net unlock := m.store.AcquireWriteLockByUID(ctx, accountID) defer unlock() + var events []func() err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { - return m.DeleteResourceInTransaction(ctx, transaction, accountID, networkID, resourceID) + events, err = m.DeleteResourceInTransaction(ctx, transaction, accountID, networkID, resourceID) + return err + }) if err != nil { return fmt.Errorf("failed to delete network resource: %w", err) } + for _, event := range events { + event() + } + go m.accountManager.UpdateAccountPeers(ctx, accountID) return nil } -func (m *managerImpl) DeleteResourceInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, resourceID string) error { +func (m *managerImpl) DeleteResourceInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, resourceID string) ([]func(), error) { resource, err := transaction.GetNetworkResourceByID(ctx, store.LockingStrengthUpdate, accountID, resourceID) if err != nil { - return fmt.Errorf("failed to get network resource: %w", err) + return nil, fmt.Errorf("failed to get network resource: %w", err) + } + + network, err := transaction.GetNetworkByID(ctx, store.LockingStrengthUpdate, accountID, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) } if resource.NetworkID != networkID { - return errors.New("resource not part of network") + return nil, errors.New("resource not part of network") } groups, err := m.groupsManager.GetResourceGroupsInTransaction(ctx, transaction, store.LockingStrengthUpdate, accountID, resourceID) if err != nil { - return fmt.Errorf("failed to get resource groups: %w", err) + return nil, fmt.Errorf("failed to get resource groups: %w", err) } + var eventsToStore []func() + for _, group := range groups { - err = m.groupsManager.RemoveResourceFromGroupInTransaction(ctx, transaction, accountID, group.ID, resourceID) + event, err := m.groupsManager.RemoveResourceFromGroupInTransaction(ctx, transaction, accountID, group.ID, resourceID) if err != nil { - return fmt.Errorf("failed to remove resource from group: %w", err) + return nil, fmt.Errorf("failed to remove resource from group: %w", err) } + eventsToStore = append(eventsToStore, event) } err = transaction.IncrementNetworkSerial(ctx, store.LockingStrengthUpdate, accountID) if err != nil { - return fmt.Errorf("failed to increment network serial: %w", err) + return nil, fmt.Errorf("failed to increment network serial: %w", err) + } + + err = transaction.DeleteNetworkResource(ctx, store.LockingStrengthUpdate, accountID, resourceID) + if err != nil { + return nil, fmt.Errorf("failed to delete network resource: %w", err) } - return transaction.DeleteNetworkResource(ctx, store.LockingStrengthUpdate, accountID, resourceID) + eventsToStore = append(eventsToStore, func() { + m.accountManager.StoreEvent(ctx, accountID, resourceID, accountID, activity.NetworkResourceDeleted, resource.EventMeta(network.Name)) + }) + + return eventsToStore, nil } diff --git a/management/server/networks/resources/types/resource.go b/management/server/networks/resources/types/resource.go index 4ea14bf82e9..c8c19c951b5 100644 --- a/management/server/networks/resources/types/resource.go +++ b/management/server/networks/resources/types/resource.go @@ -142,6 +142,10 @@ func (n *NetworkResource) ToRoute(peer *nbpeer.Peer, router *routerTypes.Network return r } +func (n *NetworkResource) EventMeta(networkName string) map[string]any { + return map[string]any{"name": n.Name, "type": n.Type, "network_name": networkName} +} + // GetResourceType returns the type of the resource based on the address func GetResourceType(address string) (NetworkResourceType, string, netip.Prefix, error) { if prefix, err := netip.ParsePrefix(address); err == nil { diff --git a/management/server/networks/routers/manager.go b/management/server/networks/routers/manager.go index 2103beb06f6..dc092d8ec35 100644 --- a/management/server/networks/routers/manager.go +++ b/management/server/networks/routers/manager.go @@ -8,7 +8,9 @@ import ( "github.com/rs/xid" s "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/networks/routers/types" + networkTypes "github.com/netbirdio/netbird/management/server/networks/types" "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/store" @@ -21,6 +23,7 @@ type Manager interface { GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) DeleteRouter(ctx context.Context, accountID, userID, networkID, routerID string) error + DeleteRouterInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, routerID string) (func(), error) } type managerImpl struct { @@ -29,6 +32,9 @@ type managerImpl struct { accountManager s.AccountManager } +type mockManager struct { +} + func NewManager(store store.Store, permissionsManager permissions.Manager, accountManager s.AccountManager) Manager { return &managerImpl{ store: store, @@ -80,13 +86,32 @@ func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *t return nil, status.NewPermissionDeniedError() } - router.ID = xid.New().String() + var network *networkTypes.Network + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + network, err = transaction.GetNetworkByID(ctx, store.LockingStrengthShare, router.AccountID, router.NetworkID) + if err != nil { + return fmt.Errorf("failed to get network: %w", err) + } + + if network.ID != router.NetworkID { + return status.NewNetworkNotFoundError(router.NetworkID) + } - err = m.store.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) + router.ID = xid.New().String() + + err = transaction.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) + if err != nil { + return fmt.Errorf("failed to create network router: %w", err) + } + + return nil + }) if err != nil { - return nil, fmt.Errorf("failed to create network router: %w", err) + return nil, err } + m.accountManager.StoreEvent(ctx, userID, router.ID, router.AccountID, activity.NetworkRouterCreated, router.EventMeta(network.Name)) + go m.accountManager.UpdateAccountPeers(ctx, router.AccountID) return router, nil @@ -122,11 +147,30 @@ func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *t return nil, status.NewPermissionDeniedError() } - err = m.store.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) + var network *networkTypes.Network + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + network, err = transaction.GetNetworkByID(ctx, store.LockingStrengthShare, router.AccountID, router.NetworkID) + if err != nil { + return fmt.Errorf("failed to get network: %w", err) + } + + if network.ID != router.NetworkID { + return status.NewRouterNotPartOfNetworkError(router.ID, router.NetworkID) + } + + err = transaction.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) + if err != nil { + return fmt.Errorf("failed to update network router: %w", err) + } + + return nil + }) if err != nil { - return nil, fmt.Errorf("failed to update network router: %w", err) + return nil, err } + m.accountManager.StoreEvent(ctx, userID, router.ID, router.AccountID, activity.NetworkRouterUpdated, router.EventMeta(network.Name)) + go m.accountManager.UpdateAccountPeers(ctx, router.AccountID) return router, nil @@ -141,12 +185,77 @@ func (m *managerImpl) DeleteRouter(ctx context.Context, accountID, userID, netwo return status.NewPermissionDeniedError() } - err = m.store.DeleteNetworkRouter(ctx, store.LockingStrengthUpdate, accountID, routerID) + var event func() + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { + event, err = m.DeleteRouterInTransaction(ctx, transaction, accountID, networkID, routerID) + return err + }) if err != nil { - return fmt.Errorf("failed to delete network router: %w", err) + return err } + event() + go m.accountManager.UpdateAccountPeers(ctx, accountID) return nil } + +func (m *managerImpl) DeleteRouterInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, routerID string) (func(), error) { + network, err := transaction.GetNetworkByID(ctx, store.LockingStrengthShare, accountID, networkID) + if err != nil { + return nil, fmt.Errorf("failed to get network: %w", err) + } + + router, err := transaction.GetNetworkRouterByID(ctx, store.LockingStrengthUpdate, accountID, routerID) + if err != nil { + return nil, fmt.Errorf("failed to get network router: %w", err) + } + + if router.NetworkID != networkID { + return nil, status.NewRouterNotPartOfNetworkError(routerID, networkID) + } + + err = transaction.DeleteNetworkRouter(ctx, store.LockingStrengthUpdate, accountID, routerID) + if err != nil { + return nil, fmt.Errorf("failed to delete network router: %w", err) + } + + event := func() { + m.accountManager.StoreEvent(ctx, "", routerID, accountID, activity.NetworkRouterDeleted, router.EventMeta(network.Name)) + } + + return event, nil +} + +func NewManagerMock() Manager { + return &mockManager{} +} + +func (m *mockManager) GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) { + return []*types.NetworkRouter{}, nil +} + +func (m *mockManager) GetAllRoutersInAccount(ctx context.Context, accountID, userID string) (map[string][]*types.NetworkRouter, error) { + return map[string][]*types.NetworkRouter{}, nil +} + +func (m *mockManager) CreateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { + return router, nil +} + +func (m *mockManager) GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) { + return &types.NetworkRouter{}, nil +} + +func (m *mockManager) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { + return router, nil +} + +func (m *mockManager) DeleteRouter(ctx context.Context, accountID, userID, networkID, routerID string) error { + return nil +} + +func (m *mockManager) DeleteRouterInTransaction(ctx context.Context, transaction store.Store, accountID, networkID, routerID string) (func(), error) { + return func() {}, nil +} diff --git a/management/server/networks/routers/types/router.go b/management/server/networks/routers/types/router.go index b1491d2d17a..4c2e11e905b 100644 --- a/management/server/networks/routers/types/router.go +++ b/management/server/networks/routers/types/router.go @@ -68,3 +68,7 @@ func (n *NetworkRouter) Copy() *NetworkRouter { Metric: n.Metric, } } + +func (n *NetworkRouter) EventMeta(networkName string) map[string]any { + return map[string]any{"network_name": networkName} +} diff --git a/management/server/networks/types/network.go b/management/server/networks/types/network.go index d9525238205..66675e325a7 100644 --- a/management/server/networks/types/network.go +++ b/management/server/networks/types/network.go @@ -49,3 +49,7 @@ func (n *Network) Copy() *Network { Description: n.Description, } } + +func (n *Network) EventMeta() map[string]any { + return map[string]any{"name": n.Name} +} diff --git a/management/server/status/error.go b/management/server/status/error.go index d65931b5a6f..d9cab02315c 100644 --- a/management/server/status/error.go +++ b/management/server/status/error.go @@ -178,3 +178,11 @@ func NewPermissionDeniedError() error { func NewPermissionValidationError(err error) error { return Errorf(PermissionDenied, "failed to vlidate user permissions: %s", err) } + +func NewResourceNotPartOfNetworkError(resourceID, networkID string) error { + return Errorf(BadRequest, "resource %s is not part of the network %s", resourceID, networkID) +} + +func NewRouterNotPartOfNetworkError(routerID, networkID string) error { + return Errorf(BadRequest, "router %s is not part of the network %s", routerID, networkID) +}