mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 12:29:09 +02:00
[management] Add support for pagination in GetAllPeers call (#8190)
* use libopenapi OpenAPI generator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added a handler for v1alpha1/peers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wire up request validator Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wired up spec-based validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use sync validation; set base url to v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moved stuff around, extracted runtime libopenapu deps into runtime_tooling Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * testing /peers path params Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fixed tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added user schemas and paths Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use api validation in tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * post-merge fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * deleted tmp command used to test validator integration Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * require request body in post/put requests in order to force validation Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * making linter happy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * switch to api/v1alpha1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * go mod tidy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * keep the original order of middleware: metrics, cors, then auth Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * return 422 on validation errors Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * v1alpha1 spec cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * no need for a double-pointer Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more linter fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * removed more double pointers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use di to inject api_v0 and api_v1 http routers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * do not re-add middleware on repeated call to ApiHandler Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * support for pagination and filtering in /peers call Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * calling ApiRouter() now also calls ApiV1Router() Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * adding peer filters Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * introduced paginated GetAllPeers call Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wire up pagination Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * convert pagination state to query scope Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * peer filter wip Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * implemented peer filter Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * test for filtering by os Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * support for sorting of peers Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * support for ordering + tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * more tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * wire up total number of records in paginated GetAllPeers call Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * cleaned up GetAllPeers response Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * pacify linter Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * cleanups and fixes Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moar cleanups Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * pacify linter Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * remove duplicated test Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * make linter happy Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moving GetAllPeersPaginated to peer manager, p. 1 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * moving GetAllPeersPaginated to peer manager, p. 2 Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * added peer manager tests for GetPeersPaginated Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * order peers in GetAccountPeersPaginated by their ids by default Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * always order by peer_id, filter out proxies Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fixed broken tests Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * support for filtering of peers by peer_id in GetAllPeers() Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * filtering by peer_ids Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * use lowercase in 'like' queries, drop ordering by ip Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * drop sort_by ip key Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * drop forced_approve filter Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * replace mac with darwin in peer os filter Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fix peers query parameter: use peer_ids Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * fixed broken peer_test Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> * responded to feedback Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> --------- Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -494,7 +495,6 @@ func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength Locking
|
||||
return &peer, nil
|
||||
}
|
||||
|
||||
// GetAccountPeers retrieves peers for an account.
|
||||
func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStrength, accountID, nameFilter, ipFilter, macFilter string) ([]*nbpeer.Peer, error) {
|
||||
var peers []*nbpeer.Peer
|
||||
tx := s.db
|
||||
@@ -514,7 +514,6 @@ func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStre
|
||||
if macFilter != "" {
|
||||
query = query.Where("meta_network_addresses LIKE ?", "%"+macFilter+"%")
|
||||
}
|
||||
|
||||
if err := query.Find(&peers).Error; err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err)
|
||||
return nil, status.Errorf(status.Internal, "failed to get peers from store")
|
||||
@@ -523,6 +522,162 @@ func (s *SqlStore) GetAccountPeers(ctx context.Context, lockStrength LockingStre
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
// GetAccountPeers retrieves peers for an account.
|
||||
func (s *SqlStore) GetAccountPeersPaginated(ctx context.Context, lockStrength LockingStrength, accountID string, pagination PaginationState, filters PeerFilters, sorting PeerSorting) ([]*nbpeer.Peer, int, error) {
|
||||
// main query
|
||||
var peers []*nbpeer.Peer
|
||||
{
|
||||
tx := s.db
|
||||
if lockStrength != LockingStrengthNone {
|
||||
tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
|
||||
}
|
||||
query := tx.Where("peers.account_id=?", accountID)
|
||||
query = query.Where("(peers.proxy_meta_embedded=false or peers.proxy_meta_embedded is null)")
|
||||
query = filters.AsConditions(query)
|
||||
query = pagination.AsScope(query)
|
||||
query = sorting.AsOrderBy(ctx, query)
|
||||
|
||||
if err := query.Distinct().Find(&peers).Error; err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get peers from the store: %s", err)
|
||||
return nil, 0, status.Errorf(status.Internal, "failed to get peers from store")
|
||||
}
|
||||
}
|
||||
|
||||
// total number of records
|
||||
var peerCount int64
|
||||
{
|
||||
tx := s.db
|
||||
if lockStrength != LockingStrengthNone {
|
||||
tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
|
||||
}
|
||||
query := tx.Where("peers.account_id=?", accountID)
|
||||
query = query.Where("(peers.proxy_meta_embedded=false or peers.proxy_meta_embedded is null)")
|
||||
query = filters.AsConditions(query)
|
||||
|
||||
if err := query.Table("peers").Select("count(distinct(peers.id))").Count(&peerCount).Error; err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get total number of peers from the store: %s", err)
|
||||
return nil, 0, status.Errorf(status.Internal, "failed to get total number of peers from store")
|
||||
}
|
||||
}
|
||||
|
||||
return peers, int(peerCount), nil
|
||||
}
|
||||
|
||||
type PaginationState struct {
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
func (p *PaginationState) AsScope(db *gorm.DB) *gorm.DB {
|
||||
// empty pagination state
|
||||
if p.PageSize == 0 {
|
||||
return db
|
||||
}
|
||||
offset := max(p.Page-1, 0) * p.PageSize
|
||||
return db.Offset(offset).Limit(p.PageSize)
|
||||
}
|
||||
|
||||
type PeerFilters struct {
|
||||
UserId string
|
||||
GroupIds []string
|
||||
Connected *bool
|
||||
ApprovalRequired *bool
|
||||
Os []string
|
||||
IP string
|
||||
MAC string
|
||||
Hostname string
|
||||
Name string
|
||||
IsServer *bool
|
||||
PeersIds []string
|
||||
}
|
||||
|
||||
func (f *PeerFilters) AsConditions(db *gorm.DB) *gorm.DB {
|
||||
if len(f.GroupIds) > 0 {
|
||||
db = db.Joins("join group_peers on peers.id=group_peers.peer_id and peers.account_id=group_peers.account_id").Where("group_peers.group_id in (?)", f.GroupIds)
|
||||
}
|
||||
if f.UserId != "" {
|
||||
db = db.Where("user_id=?", f.UserId)
|
||||
}
|
||||
if f.Connected != nil {
|
||||
db = db.Where("peer_status_connected=?", *f.Connected)
|
||||
}
|
||||
if f.ApprovalRequired != nil {
|
||||
db = db.Where("peer_status_requires_approval=?", *f.ApprovalRequired)
|
||||
}
|
||||
if len(f.Os) > 0 {
|
||||
db = db.Where("meta_go_os in (?)", f.Os)
|
||||
}
|
||||
if f.IP != "" {
|
||||
db = db.Where("ip like ? or lower(ipv6) like ?", fmt.Sprintf("%%%s%%", f.IP), fmt.Sprintf("%%%s%%", strings.ToLower(f.IP)))
|
||||
}
|
||||
if f.MAC != "" {
|
||||
db = db.Where("lower(meta_network_addresses) like ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.MAC)))
|
||||
}
|
||||
if f.Hostname != "" {
|
||||
db = db.Where("lower(meta_hostname) like ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Hostname)))
|
||||
}
|
||||
if f.Name != "" {
|
||||
db = db.Where("lower(name) like ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Name)))
|
||||
}
|
||||
if f.IsServer != nil {
|
||||
if *f.IsServer {
|
||||
db = db.Where("(user_id='' or user_id is null)")
|
||||
} else {
|
||||
db = db.Where("user_id<>'' and user_id is not null")
|
||||
}
|
||||
}
|
||||
if len(f.PeersIds) > 0 {
|
||||
db = db.Where("id in (?)", f.PeersIds)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
type PeerSortByField int
|
||||
|
||||
const (
|
||||
PeerSortByName PeerSortByField = iota
|
||||
PeerSortByLastSeen
|
||||
PeerSortByVersion
|
||||
)
|
||||
|
||||
type OrderBy int
|
||||
|
||||
const (
|
||||
AscOrderBy OrderBy = iota
|
||||
DescOrderBy
|
||||
)
|
||||
|
||||
type PeerSortingKey struct {
|
||||
Field PeerSortByField
|
||||
Order OrderBy
|
||||
}
|
||||
|
||||
type PeerSorting struct {
|
||||
Fields []PeerSortingKey
|
||||
}
|
||||
|
||||
func (s *PeerSorting) AsOrderBy(ctx context.Context, db *gorm.DB) *gorm.DB {
|
||||
order := make([]clause.OrderByColumn, 0)
|
||||
|
||||
for _, key := range s.Fields {
|
||||
switch key.Field {
|
||||
case PeerSortByName:
|
||||
order = append(order, clause.OrderByColumn{Column: clause.Column{Name: "peers.name"}, Desc: key.Order == DescOrderBy})
|
||||
case PeerSortByLastSeen:
|
||||
order = append(order, clause.OrderByColumn{Column: clause.Column{Name: "peers.peer_status_last_seen"}, Desc: key.Order == DescOrderBy})
|
||||
case PeerSortByVersion:
|
||||
order = append(order, clause.OrderByColumn{Column: clause.Column{Name: "meta_wt_version"}, Desc: key.Order == DescOrderBy})
|
||||
default:
|
||||
log.WithContext(ctx).Warnf("invalid order by column '%v', skipping", key.Field)
|
||||
}
|
||||
}
|
||||
|
||||
// always order/break ties by id asc order
|
||||
order = append(order, clause.OrderByColumn{Column: clause.Column{Name: "peers.id"}, Desc: false})
|
||||
return db.Order(clause.OrderBy{Columns: order})
|
||||
}
|
||||
|
||||
// GetUserPeers retrieves peers for a user.
|
||||
func (s *SqlStore) GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error) {
|
||||
tx := s.db
|
||||
|
||||
Reference in New Issue
Block a user