mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-02 11:29:46 +01:00
Remove usage stats (#1665)
This commit is contained in:
parent
17f5abc653
commit
199bf73103
@ -72,7 +72,6 @@ type AccountManager interface {
|
|||||||
CheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error
|
CheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error
|
||||||
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
||||||
DeleteAccount(accountID, userID string) error
|
DeleteAccount(accountID, userID string) error
|
||||||
GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error)
|
|
||||||
MarkPATUsed(tokenID string) error
|
MarkPATUsed(tokenID string) error
|
||||||
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
||||||
ListUsers(accountID string) ([]*User, error)
|
ListUsers(accountID string) ([]*User, error)
|
||||||
@ -233,14 +232,6 @@ type Account struct {
|
|||||||
RulesG []Rule `json:"-" gorm:"-"`
|
RulesG []Rule `json:"-" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUsageStats represents the current usage statistics for an account
|
|
||||||
type AccountUsageStats struct {
|
|
||||||
ActiveUsers int64 `json:"active_users"`
|
|
||||||
TotalUsers int64 `json:"total_users"`
|
|
||||||
ActivePeers int64 `json:"active_peers"`
|
|
||||||
TotalPeers int64 `json:"total_peers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
@ -1121,17 +1112,6 @@ func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage returns the usage stats for the given account.
|
|
||||||
// This cannot be used to calculate usage stats for a period in the past as it relies on peers' last seen time.
|
|
||||||
func (am *DefaultAccountManager) GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
usageStats, err := am.Store.CalculateUsageStats(ctx, accountID, start, end)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to calculate usage stats: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return usageStats, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
|
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
|
||||||
// userID doesn't have an account associated with it, one account is created
|
// userID doesn't have an account associated with it, one account is created
|
||||||
// domain is used to create a new account if no account is found
|
// domain is used to create a new account if no account is found
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -664,40 +662,3 @@ func (s *FileStore) Close() error {
|
|||||||
func (s *FileStore) GetStoreEngine() StoreEngine {
|
func (s *FileStore) GetStoreEngine() StoreEngine {
|
||||||
return FileStoreEngine
|
return FileStoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateUsageStats returns the usage stats for an account
|
|
||||||
// start and end are inclusive.
|
|
||||||
func (s *FileStore) CalculateUsageStats(_ context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
account, exists := s.Accounts[accountID]
|
|
||||||
if !exists {
|
|
||||||
return nil, fmt.Errorf("account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := &AccountUsageStats{
|
|
||||||
TotalUsers: 0,
|
|
||||||
TotalPeers: int64(len(account.Peers)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range account.Users {
|
|
||||||
if !user.IsServiceUser {
|
|
||||||
stats.TotalUsers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeUsers := make(map[string]bool)
|
|
||||||
for _, peer := range account.Peers {
|
|
||||||
lastSeen := peer.Status.LastSeen
|
|
||||||
if lastSeen.Compare(start) >= 0 && lastSeen.Compare(end) <= 0 {
|
|
||||||
if _, exists := account.Users[peer.UserID]; exists && !activeUsers[peer.UserID] {
|
|
||||||
activeUsers[peer.UserID] = true
|
|
||||||
stats.ActiveUsers++
|
|
||||||
}
|
|
||||||
stats.ActivePeers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -658,32 +657,3 @@ func newStore(t *testing.T) *FileStore {
|
|||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStore_CalculateUsageStats(t *testing.T) {
|
|
||||||
storeDir := t.TempDir()
|
|
||||||
|
|
||||||
err := util.CopyFileContents("testdata/store_stats.json", filepath.Join(storeDir, "store.json"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
store, err := NewFileStore(storeDir, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
startDate := time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
endDate := startDate.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
||||||
|
|
||||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
|
||||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
|
||||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
|
||||||
|
|
||||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
|
||||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package mock_server
|
package mock_server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -93,7 +92,6 @@ type MockAccountManager struct {
|
|||||||
SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error
|
SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error
|
||||||
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
||||||
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
||||||
GetUsageFunc func(ctx context.Context, accountID string, start, end time.Time) (*server.AccountUsageStats, error)
|
|
||||||
GetIdpManagerFunc func() idp.Manager
|
GetIdpManagerFunc func() idp.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -707,14 +705,6 @@ func (am *MockAccountManager) ListPostureChecks(accountID, userID string) ([]*po
|
|||||||
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage mocks GetUsage of the AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*server.AccountUsageStats, error) {
|
|
||||||
if am.GetUsageFunc != nil {
|
|
||||||
return am.GetUsageFunc(ctx, accountID, start, end)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsage is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIdpManager mocks GetIdpManager of the AccountManager interface
|
// GetIdpManager mocks GetIdpManager of the AccountManager interface
|
||||||
func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
||||||
if am.GetIdpManagerFunc != nil {
|
if am.GetIdpManagerFunc != nil {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -498,48 +497,3 @@ func (s *SqliteStore) Close() error {
|
|||||||
func (s *SqliteStore) GetStoreEngine() StoreEngine {
|
func (s *SqliteStore) GetStoreEngine() StoreEngine {
|
||||||
return SqliteStoreEngine
|
return SqliteStoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateUsageStats returns the usage stats for an account
|
|
||||||
// start and end are inclusive.
|
|
||||||
func (s *SqliteStore) CalculateUsageStats(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
stats := &AccountUsageStats{}
|
|
||||||
|
|
||||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
err := tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ? AND peer_status_last_seen BETWEEN ? AND ?", accountID, start, end).
|
|
||||||
Distinct("user_id").
|
|
||||||
Count(&stats.ActiveUsers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get active users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&User{}).
|
|
||||||
Where("account_id = ? AND is_service_user = ?", accountID, false).
|
|
||||||
Count(&stats.TotalUsers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get total users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ? AND peer_status_last_seen BETWEEN ? AND ?", accountID, start, end).
|
|
||||||
Count(&stats.ActivePeers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get active peers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ?", accountID).
|
|
||||||
Count(&stats.TotalPeers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get total peers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -347,29 +346,3 @@ func newAccount(store Store, id int) error {
|
|||||||
|
|
||||||
return store.SaveAccount(account)
|
return store.SaveAccount(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqliteStore_CalculateUsageStats(t *testing.T) {
|
|
||||||
store := newSqliteStoreFromFile(t, "testdata/store_stats.json")
|
|
||||||
t.Cleanup(func() {
|
|
||||||
require.NoError(t, store.Close())
|
|
||||||
})
|
|
||||||
|
|
||||||
startDate := time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
endDate := startDate.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
||||||
|
|
||||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
|
||||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
|
||||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
|
||||||
|
|
||||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
|
||||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -42,7 +41,6 @@ type Store interface {
|
|||||||
// GetStoreEngine should return StoreEngine of the current store implementation.
|
// GetStoreEngine should return StoreEngine of the current store implementation.
|
||||||
// This is also a method of metrics.DataSource interface.
|
// This is also a method of metrics.DataSource interface.
|
||||||
GetStoreEngine() StoreEngine
|
GetStoreEngine() StoreEngine
|
||||||
CalculateUsageStats(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoreEngine string
|
type StoreEngine string
|
||||||
|
161
management/server/testdata/store_stats.json
vendored
161
management/server/testdata/store_stats.json
vendored
@ -1,161 +0,0 @@
|
|||||||
{
|
|
||||||
"Accounts": {
|
|
||||||
"account-1": {
|
|
||||||
"Id": "account-1",
|
|
||||||
"Domain": "example.com",
|
|
||||||
"Network": {
|
|
||||||
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
|
||||||
"Net": {
|
|
||||||
"IP": "100.64.0.0",
|
|
||||||
"Mask": "//8AAA=="
|
|
||||||
},
|
|
||||||
"Dns": null
|
|
||||||
},
|
|
||||||
"Users": {
|
|
||||||
"user-1-account-1": {
|
|
||||||
"Id": "user-1-account-1"
|
|
||||||
},
|
|
||||||
"user-2-account-1": {
|
|
||||||
"Id": "user-2-account-1"
|
|
||||||
},
|
|
||||||
"user-3-account-1": {
|
|
||||||
"Id": "user-3-account-1"
|
|
||||||
},
|
|
||||||
"user-4-account-1": {
|
|
||||||
"Id": "user-4-account-1"
|
|
||||||
},
|
|
||||||
"user-5-account-1": {
|
|
||||||
"Id": "user-5-account-1",
|
|
||||||
"IsServiceUser": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Peers": {
|
|
||||||
"peer-1-account-1": {
|
|
||||||
"ID": "peer-1-account-1",
|
|
||||||
"UserID": "user-1-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-01-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer One",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer1-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-2-account-1": {
|
|
||||||
"ID": "peer-2-account-1",
|
|
||||||
"UserID": "user-2-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-29T23:59:59Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Two",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer2-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-3-account-1": {
|
|
||||||
"ID": "peer-3-account-1",
|
|
||||||
"UserID": "user-2-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-01T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Three",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer3-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-4-account-1": {
|
|
||||||
"ID": "peer-4-account-1",
|
|
||||||
"UserID": "user-3-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-08T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Four",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer4-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-5-account-1": {
|
|
||||||
"ID": "peer-5-account-1",
|
|
||||||
"UserID": "user-3-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2023-06-01T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Five",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer5-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-6-account-1": {
|
|
||||||
"ID": "peer-6-account-1",
|
|
||||||
"UserID": "user-4-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-01-31T23:59:59Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Six",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer6-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-7-account-1": {
|
|
||||||
"ID": "peer-7-account-1",
|
|
||||||
"UserID": "user-4-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-03-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Seven",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer7-host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"account-2": {
|
|
||||||
"Id": "account-2",
|
|
||||||
"Domain": "example.org",
|
|
||||||
"Network": {
|
|
||||||
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
|
||||||
"Net": {
|
|
||||||
"IP": "100.64.0.0",
|
|
||||||
"Mask": "//8AAA=="
|
|
||||||
},
|
|
||||||
"Dns": null
|
|
||||||
},
|
|
||||||
"Users": {
|
|
||||||
"user-1-account-2": {
|
|
||||||
"Id": "user-1-account-2"
|
|
||||||
},
|
|
||||||
"user-2-account-2": {
|
|
||||||
"Id": "user-1-account-2"
|
|
||||||
},
|
|
||||||
"user-3-account-2": {
|
|
||||||
"Id": "user-3-account-2",
|
|
||||||
"IsServiceUser": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Peers": {
|
|
||||||
"peer-1-account-2": {
|
|
||||||
"ID": "peer-1-account-2",
|
|
||||||
"UserID": "user-1-account-2",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2023-08-30T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer One",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer1-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-2-account-2": {
|
|
||||||
"ID": "peer-2-account-2",
|
|
||||||
"UserID": "user-1-account-2",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-08T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Two",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer2-host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user