feat: add standalone controlplane support
This commit is contained in:
@@ -4,13 +4,13 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
"github.com/cedar2025/xboard-node/internal/model"
|
||||
)
|
||||
|
||||
// Limiter enforces per-user device limits and detects removed users.
|
||||
type Limiter struct {
|
||||
mu sync.RWMutex
|
||||
users map[int]panel.User
|
||||
users map[int]model.UserSpec
|
||||
|
||||
// uuidDeviceLimit provides O(1) UUID→device-limit lookup for the
|
||||
// kernel gate-keeping hot path (called per new connection).
|
||||
@@ -21,18 +21,18 @@ type Limiter struct {
|
||||
|
||||
func New() *Limiter {
|
||||
return &Limiter{
|
||||
users: make(map[int]panel.User),
|
||||
users: make(map[int]model.UserSpec),
|
||||
uuidDeviceLimit: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUsers refreshes the user limit configuration and returns
|
||||
// the IDs of users that were present before but are now removed.
|
||||
func (l *Limiter) UpdateUsers(users []panel.User) []int {
|
||||
func (l *Limiter) UpdateUsers(users []model.UserSpec) []int {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
newUsers := make(map[int]panel.User, len(users))
|
||||
newUsers := make(map[int]model.UserSpec, len(users))
|
||||
for _, u := range users {
|
||||
newUsers[u.ID] = u
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
"github.com/cedar2025/xboard-node/internal/model"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
@@ -16,7 +16,7 @@ func TestNew(t *testing.T) {
|
||||
|
||||
func TestUpdateUsers(t *testing.T) {
|
||||
l := New()
|
||||
users := []panel.User{
|
||||
users := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 3, DeviceLimit: 2},
|
||||
{ID: 2, UUID: "u2", SpeedLimit: 0, DeviceLimit: 0},
|
||||
}
|
||||
@@ -29,14 +29,14 @@ func TestUpdateUsers(t *testing.T) {
|
||||
|
||||
func TestUpdateUsers_DetectsRemoved(t *testing.T) {
|
||||
l := New()
|
||||
l.UpdateUsers([]panel.User{
|
||||
l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1"},
|
||||
{ID: 2, UUID: "u2"},
|
||||
{ID: 3, UUID: "u3"},
|
||||
})
|
||||
|
||||
// Remove user 2
|
||||
removed := l.UpdateUsers([]panel.User{
|
||||
removed := l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1"},
|
||||
{ID: 3, UUID: "u3"},
|
||||
})
|
||||
@@ -48,14 +48,14 @@ func TestUpdateUsers_DetectsRemoved(t *testing.T) {
|
||||
|
||||
func TestUpdateUsers_DetectsMultipleRemoved(t *testing.T) {
|
||||
l := New()
|
||||
l.UpdateUsers([]panel.User{
|
||||
l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1"},
|
||||
{ID: 2, UUID: "u2"},
|
||||
{ID: 3, UUID: "u3"},
|
||||
})
|
||||
|
||||
// Remove users 1 and 3
|
||||
removed := l.UpdateUsers([]panel.User{
|
||||
removed := l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 2, UUID: "u2"},
|
||||
})
|
||||
|
||||
@@ -67,12 +67,12 @@ func TestUpdateUsers_DetectsMultipleRemoved(t *testing.T) {
|
||||
|
||||
func TestUpdateUsers_NoRemovals(t *testing.T) {
|
||||
l := New()
|
||||
l.UpdateUsers([]panel.User{
|
||||
l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1"},
|
||||
})
|
||||
|
||||
// Add user, keep existing
|
||||
removed := l.UpdateUsers([]panel.User{
|
||||
removed := l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1"},
|
||||
{ID: 2, UUID: "u2"},
|
||||
})
|
||||
@@ -84,10 +84,10 @@ func TestUpdateUsers_NoRemovals(t *testing.T) {
|
||||
|
||||
func TestUpdateUsers_ReplacesAll(t *testing.T) {
|
||||
l := New()
|
||||
l.UpdateUsers([]panel.User{{ID: 1, DeviceLimit: 1}})
|
||||
l.UpdateUsers([]model.UserSpec{{ID: 1, DeviceLimit: 1}})
|
||||
|
||||
// Replace with different users
|
||||
removed := l.UpdateUsers([]panel.User{{ID: 2, DeviceLimit: 1}})
|
||||
removed := l.UpdateUsers([]model.UserSpec{{ID: 2, DeviceLimit: 1}})
|
||||
|
||||
if len(removed) != 1 || removed[0] != 1 {
|
||||
t.Errorf("expected removed=[1], got %v", removed)
|
||||
@@ -96,7 +96,7 @@ func TestUpdateUsers_ReplacesAll(t *testing.T) {
|
||||
|
||||
func TestGetDeviceLimitByUUID(t *testing.T) {
|
||||
l := New()
|
||||
l.UpdateUsers([]panel.User{
|
||||
l.UpdateUsers([]model.UserSpec{
|
||||
{ID: 1, UUID: "u1", DeviceLimit: 3},
|
||||
{ID: 2, UUID: "u2", DeviceLimit: 0},
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
"github.com/cedar2025/xboard-node/internal/model"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *SpeedTracker) SetLogCallback(f SpeedTrackerLogCallback) {
|
||||
|
||||
// UpdateBuckets updates the UUID→userID mapping and syncs existing limiters.
|
||||
func (t *SpeedTracker) UpdateBuckets() {
|
||||
currentUsers := make([]panel.User, 0, 32)
|
||||
currentUsers := make([]model.UserSpec, 0, 32)
|
||||
t.limiter.mu.RLock()
|
||||
for _, u := range t.limiter.users {
|
||||
currentUsers = append(currentUsers, u)
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cedar2025/xboard-node/internal/panel"
|
||||
"github.com/cedar2025/xboard-node/internal/model"
|
||||
)
|
||||
|
||||
func TestSpeedTracker_UpdateBuckets(t *testing.T) {
|
||||
l := New()
|
||||
st := NewSpeedTracker(l)
|
||||
|
||||
users := []panel.User{
|
||||
users := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 3, DeviceLimit: 0}, // 3 Mbps
|
||||
{ID: 2, UUID: "u2", SpeedLimit: 0, DeviceLimit: 0}, // unlimited
|
||||
{ID: 3, UUID: "u3", SpeedLimit: 10, DeviceLimit: 2}, // 10 Mbps
|
||||
@@ -60,7 +60,7 @@ func TestSpeedTracker_UpdateBuckets_PreservesExisting(t *testing.T) {
|
||||
l := New()
|
||||
st := NewSpeedTracker(l)
|
||||
|
||||
users := []panel.User{
|
||||
users := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 3, DeviceLimit: 0},
|
||||
}
|
||||
l.UpdateUsers(users)
|
||||
@@ -72,7 +72,7 @@ func TestSpeedTracker_UpdateBuckets_PreservesExisting(t *testing.T) {
|
||||
}
|
||||
|
||||
// Update same user with different speed — should reuse same *rate.Limiter pointer
|
||||
users2 := []panel.User{
|
||||
users2 := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 5, DeviceLimit: 0},
|
||||
}
|
||||
l.UpdateUsers(users2)
|
||||
@@ -97,7 +97,7 @@ func TestSpeedTracker_UpdateBuckets_RemovesUsers(t *testing.T) {
|
||||
l := New()
|
||||
st := NewSpeedTracker(l)
|
||||
|
||||
users := []panel.User{
|
||||
users := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 3, DeviceLimit: 0},
|
||||
{ID: 2, UUID: "u2", SpeedLimit: 5, DeviceLimit: 0},
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func TestSpeedTracker_UpdateBuckets_RemovesUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
// Remove user 2
|
||||
users2 := []panel.User{
|
||||
users2 := []model.UserSpec{
|
||||
{ID: 1, UUID: "u1", SpeedLimit: 3, DeviceLimit: 0},
|
||||
}
|
||||
l.UpdateUsers(users2)
|
||||
@@ -128,7 +128,7 @@ func TestSpeedTracker_UpdateBuckets_RemovesUsers(t *testing.T) {
|
||||
func TestSpeedTracker_UpdateBuckets_LogCallbackMayCallLimitedUserCount(t *testing.T) {
|
||||
l := New()
|
||||
st := NewSpeedTracker(l)
|
||||
l.UpdateUsers([]panel.User{{ID: 1, UUID: "u1", SpeedLimit: 1}})
|
||||
l.UpdateUsers([]model.UserSpec{{ID: 1, UUID: "u1", SpeedLimit: 1}})
|
||||
st.SetLogCallback(func(msg string) {
|
||||
_ = st.LimitedUserCount()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user