mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-04 03:25:19 -04:00
## Describe your changes `NB_LAZY_CONN_INACTIVITY_THRESHOLD` was parsed with `strconv.Atoi`, i.e. as a bare integer number of minutes. The documentation, however, states it takes a Go duration (e.g. `30m`, `1h`). As a result any documented value such as `30m` or `5m` failed to parse and **silently fell back to the 15m default**, so the setting appeared to have no effect. `inactivityThresholdEnv()` now parses the value with `time.ParseDuration`, matching the docs. A bare integer is still accepted as a number of minutes for backwards compatibility, and an unparseable value logs a warning and falls back to the default. Added `TestInactivityThresholdEnv` covering Go-duration values (`30m`/`1h`/`90s`), the bare-integer minutes fallback, and zero/negative/garbage inputs. ## Issue ticket number and link N/A
142 lines
4.5 KiB
Go
142 lines
4.5 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
|
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
|
"github.com/netbirdio/netbird/client/internal/lazyconn"
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
"github.com/netbirdio/netbird/client/internal/peerstore"
|
|
"github.com/netbirdio/netbird/monotime"
|
|
)
|
|
|
|
func TestResolveLazyForce(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env string
|
|
envSet bool
|
|
mdm lazyconn.State
|
|
want lazyForce
|
|
}{
|
|
{name: "env unset, mdm unset -> defer to management", mdm: lazyconn.StateUnset, want: lazyForceNone},
|
|
{name: "env on -> force on", env: "on", envSet: true, mdm: lazyconn.StateUnset, want: lazyForceOn},
|
|
{name: "env off -> force off", env: "off", envSet: true, mdm: lazyconn.StateUnset, want: lazyForceOff},
|
|
{name: "env unset, mdm on -> force on", mdm: lazyconn.StateOn, want: lazyForceOn},
|
|
{name: "env unset, mdm off -> force off", mdm: lazyconn.StateOff, want: lazyForceOff},
|
|
{name: "env on beats mdm off", env: "on", envSet: true, mdm: lazyconn.StateOff, want: lazyForceOn},
|
|
{name: "env off beats mdm on", env: "off", envSet: true, mdm: lazyconn.StateOn, want: lazyForceOff},
|
|
{name: "unrecognized env, mdm on -> mdm wins", env: "auto", envSet: true, mdm: lazyconn.StateOn, want: lazyForceOn},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv(lazyconn.EnvLazyConn, tt.env)
|
|
if !tt.envSet {
|
|
os.Unsetenv(lazyconn.EnvLazyConn)
|
|
}
|
|
|
|
if got := resolveLazyForce(tt.mdm); got != tt.want {
|
|
t.Fatalf("resolveLazyForce(%v) = %v, want %v", tt.mdm, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type mockLazyWGIface struct{}
|
|
|
|
func (mockLazyWGIface) RemovePeer(string) error { return nil }
|
|
func (mockLazyWGIface) UpdatePeer(string, []netip.Prefix, time.Duration, *net.UDPAddr, *wgtypes.Key) error {
|
|
return nil
|
|
}
|
|
func (mockLazyWGIface) IsUserspaceBind() bool { return false }
|
|
func (mockLazyWGIface) Address() wgaddr.Address { return wgaddr.Address{} }
|
|
func (mockLazyWGIface) LastActivities() map[string]monotime.Time { return nil }
|
|
func (mockLazyWGIface) MTU() uint16 { return 1280 }
|
|
|
|
// TestConnMgr_ActivatePeerConcurrentWithLifecycle exercises ActivatePeer from
|
|
// non-engine goroutines (the DNS warm-up path) racing the manager lifecycle,
|
|
// which stays on the engine loop. Run with -race: it fails if ActivatePeer
|
|
// still requires engine.syncMsgMux for safety.
|
|
func TestConnMgr_ActivatePeerConcurrentWithLifecycle(t *testing.T) {
|
|
t.Setenv(lazyconn.EnvLazyConn, "on")
|
|
|
|
status := peer.NewRecorder("https://mgm")
|
|
store := peerstore.NewConnStore()
|
|
connMgr := NewConnMgr(&EngineConfig{}, status, store, mockLazyWGIface{})
|
|
|
|
conn := newTestPeerConn(t, "peerA")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
connMgr.Start(ctx)
|
|
|
|
done := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
for range 4 {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
connMgr.ActivatePeer(ctx, conn)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Let the activators spin against the started manager, then tear it down
|
|
// underneath them and let them spin against the stopped manager.
|
|
time.Sleep(100 * time.Millisecond)
|
|
connMgr.Close()
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
close(done)
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestInactivityThresholdEnv(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
val string
|
|
want *time.Duration
|
|
}{
|
|
{name: "unset", val: "", want: nil},
|
|
{name: "go duration minutes", val: "30m", want: durPtr(30 * time.Minute)},
|
|
{name: "go duration hours", val: "1h", want: durPtr(time.Hour)},
|
|
{name: "go duration seconds", val: "90s", want: durPtr(90 * time.Second)},
|
|
{name: "bare integer is minutes (backwards compat)", val: "5", want: durPtr(5 * time.Minute)},
|
|
{name: "zero duration", val: "0s", want: nil},
|
|
{name: "zero integer", val: "0", want: nil},
|
|
{name: "negative duration", val: "-5m", want: nil},
|
|
{name: "garbage", val: "abc", want: nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Setenv(lazyconn.EnvInactivityThreshold, tc.val)
|
|
got := inactivityThresholdEnv()
|
|
switch {
|
|
case tc.want == nil && got != nil:
|
|
t.Fatalf("want nil, got %v", *got)
|
|
case tc.want != nil && got == nil:
|
|
t.Fatalf("want %v, got nil", *tc.want)
|
|
case tc.want != nil && *got != *tc.want:
|
|
t.Fatalf("want %v, got %v", *tc.want, *got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func durPtr(d time.Duration) *time.Duration { return &d }
|