mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 06:39:08 +02:00
Resolve conflicts in shared/relay/client/manager.go: - NewManager: keep both sides. main's serverPicker.NetEvents wiring and this branch's ForeignRelaysStore construction are independent. - openConnVia/openConnOnTrack: take this branch's deletion. main refined the RelayTrack rt.ready locking, but this branch replaces that machinery with the singleflight-based ForeignRelaysStore, so the refinement applies to a code path that no longer exists. Carry main's netEvents plumbing into the new store so foreign relay dials are still registered for the network change sweep.
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newTestForeignStore(t *testing.T, ctx context.Context) *ForeignRelaysStore {
|
|
t.Helper()
|
|
return NewForeignRelaysStore(ctx, hmacTokenStore, "alice", 1280, newTransportFallback(), nil, func(string) {}, keepUnusedServerTime)
|
|
}
|
|
|
|
func TestForeignStore_AcquireDedupsConcurrentOpens(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52601")
|
|
server := RelayServer{Addr: "rel://" + addr}
|
|
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
const n = 8
|
|
var wg sync.WaitGroup
|
|
results := make([]*foreignRelay, n)
|
|
for i := 0; i < n; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
fr, err := store.acquire(server)
|
|
require.NoError(t, err)
|
|
results[i] = fr
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
first := results[0]
|
|
require.NotNil(t, first)
|
|
for _, fr := range results {
|
|
require.Same(t, first, fr, "all acquires must share the same foreign relay")
|
|
}
|
|
|
|
store.mu.RLock()
|
|
require.Len(t, store.clients, 1, "only one client entry must be stored")
|
|
require.Equal(t, n, first.inUse, "every acquire must be counted")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_AcquireReleaseRefcount(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52602")
|
|
server := RelayServer{Addr: "rel://" + addr}
|
|
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
fr, err := store.acquire(server)
|
|
require.NoError(t, err)
|
|
_, err = store.acquire(server)
|
|
require.NoError(t, err)
|
|
|
|
store.mu.RLock()
|
|
require.Equal(t, 2, fr.inUse)
|
|
store.mu.RUnlock()
|
|
|
|
store.release(fr)
|
|
store.mu.RLock()
|
|
require.Equal(t, 1, fr.inUse)
|
|
require.Len(t, store.clients, 1, "release must not evict the client")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_AcquireConnectFailure(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
// Nothing is listening on this port, so Connect fails.
|
|
_, err := store.acquire(RelayServer{Addr: "rel://127.0.0.1:1"})
|
|
require.Error(t, err)
|
|
|
|
store.mu.RLock()
|
|
require.Empty(t, store.clients, "a failed connect must not leave a client behind")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_Evict(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
store := newTestForeignStore(t, ctx)
|
|
store.clients["rel://a"] = &foreignRelay{}
|
|
store.clients["rel://b"] = &foreignRelay{}
|
|
|
|
store.evict("rel://a")
|
|
store.evict("rel://missing")
|
|
|
|
require.NotContains(t, store.clients, "rel://a")
|
|
require.Contains(t, store.clients, "rel://b")
|
|
}
|
|
|
|
func TestForeignStore_CleanupUnused_KeepsRecent(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52603")
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
fr, err := store.acquire(RelayServer{Addr: "rel://" + addr})
|
|
require.NoError(t, err)
|
|
store.release(fr)
|
|
|
|
store.cleanupUnused()
|
|
|
|
store.mu.RLock()
|
|
require.Len(t, store.clients, 1, "a freshly created client must be kept")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_CleanupUnused_KeepsInUse(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52604")
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
fr, err := store.acquire(RelayServer{Addr: "rel://" + addr})
|
|
require.NoError(t, err)
|
|
|
|
store.mu.Lock()
|
|
fr.created = time.Now().Add(-2 * keepUnusedServerTime)
|
|
store.mu.Unlock()
|
|
|
|
store.cleanupUnused()
|
|
|
|
store.mu.RLock()
|
|
require.Len(t, store.clients, 1, "an in-use client must be kept even when aged")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_CleanupUnused_EvictsAgedIdle(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52605")
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
fr, err := store.acquire(RelayServer{Addr: "rel://" + addr})
|
|
require.NoError(t, err)
|
|
store.release(fr)
|
|
|
|
store.mu.Lock()
|
|
fr.created = time.Now().Add(-2 * keepUnusedServerTime)
|
|
store.mu.Unlock()
|
|
|
|
require.False(t, fr.client.HasConns(), "no peer connections were opened")
|
|
|
|
store.cleanupUnused()
|
|
|
|
store.mu.RLock()
|
|
require.Empty(t, store.clients, "an aged idle client must be evicted")
|
|
store.mu.RUnlock()
|
|
}
|
|
|
|
func TestForeignStore_States(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(cancel)
|
|
|
|
addr := startTestRelayServer(t, "127.0.0.1:52606")
|
|
store := newTestForeignStore(t, ctx)
|
|
|
|
fr, err := store.acquire(RelayServer{Addr: "rel://" + addr})
|
|
require.NoError(t, err)
|
|
store.release(fr)
|
|
|
|
states := store.states()
|
|
require.Len(t, states, 1)
|
|
require.NotEmpty(t, states[0].URL)
|
|
}
|