MWSE/internal/services/ippressure_test.go

142 lines
3.5 KiB
Go

package services
import (
"strings"
"sync"
"testing"
)
// TestRandomAllocationIsUnique verifies that 1000 concurrent IP allocations
// produce no duplicates (the random probing strategy must be collision-safe).
func TestRandomAllocationIsUnique(t *testing.T) {
p := NewIPPressure(nil)
const n = 1000
results := make([]string, n)
var wg sync.WaitGroup
for i := range n {
wg.Add(1)
go func(i int) {
defer wg.Done()
results[i] = p.lockIP("client-" + string(rune('A'+i%26)) + string(rune('0'+i/26)))
}(i)
}
wg.Wait()
seen := make(map[string]int, n)
for i, ip := range results {
if ip == "" {
t.Fatalf("allocation %d returned empty string", i)
}
if prev, dup := seen[ip]; dup {
t.Fatalf("duplicate IP %s at slots %d and %d", ip, prev, i)
}
seen[ip] = i
}
}
// TestRandomNumberAllocation checks uniqueness for number allocations.
func TestRandomNumberAllocation(t *testing.T) {
p := NewIPPressure(nil)
const n = 500
nums := make([]int, n)
var wg sync.WaitGroup
for i := range n {
wg.Add(1)
go func(i int) {
defer wg.Done()
nums[i] = p.lockNumber("c" + string(rune('A'+i%26)))
}(i)
}
wg.Wait()
seen := make(map[int]bool, n)
for _, n := range nums {
if seen[n] {
t.Fatalf("duplicate number %d", n)
}
seen[n] = true
}
}
// TestSubNetAllocRelease covers the /24 subnet lifecycle.
func TestSubNetAllocRelease(t *testing.T) {
hub := newHub()
a, _ := connect(hub, "a")
b, _ := connect(hub, "b")
// Allocate a subnet.
resp := asMap(t, hub.Handle(a, msg("alloc/APSubNet")))
if resp["status"] != "success" {
t.Fatalf("alloc/APSubNet = %v", resp)
}
prefix := resp["prefix"].(string)
if !strings.HasPrefix(prefix, "10.") {
t.Fatalf("prefix %q should start with '10.'", prefix)
}
// Idempotent: second alloc returns the same prefix.
resp2 := asMap(t, hub.Handle(a, msg("alloc/APSubNet")))
if resp2["prefix"] != prefix {
t.Fatalf("second alloc/APSubNet should return same prefix, got %v vs %v", resp2["prefix"], prefix)
}
// b requests a host IP within a's subnet.
ipResp := asMap(t, hub.Handle(b, msg("alloc/APSubNetIP", "prefix", prefix)))
if ipResp["status"] != "success" {
t.Fatalf("alloc/APSubNetIP = %v", ipResp)
}
ip := ipResp["ip"].(string)
if !strings.HasPrefix(ip, prefix+".") {
t.Fatalf("allocated IP %q should be within prefix %q", ip, prefix)
}
// whois query finds b.
who := asMap(t, hub.Handle(a, msg("whois/APSubNetIP", "prefix", prefix, "whois", ip)))
if who["socket"] != "b" {
t.Fatalf("whois/APSubNetIP = %v, want b", who)
}
// Release b's host IP.
if r := asMap(t, hub.Handle(b, msg("release/APSubNetIP"))); r["status"] != "success" {
t.Fatalf("release/APSubNetIP = %v", r)
}
// Release a's subnet.
if r := asMap(t, hub.Handle(a, msg("release/APSubNet"))); r["status"] != "success" {
t.Fatalf("release/APSubNet = %v", r)
}
}
// TestSubNetIPsAreUnique verifies that multiple clients get distinct IPs
// within the same subnet.
func TestSubNetIPsAreUnique(t *testing.T) {
p := NewIPPressure(nil)
sn, ok := p.allocSubNet("owner")
if !ok {
t.Fatal("allocSubNet failed")
}
const n = 50
ips := make([]string, n)
for i := range n {
ip, ok := sn.Alloc("client-" + string(rune('A'+i)))
if !ok {
t.Fatalf("Alloc failed at %d", i)
}
ips[i] = ip
}
seen := make(map[string]bool, n)
for _, ip := range ips {
if seen[ip] {
t.Fatalf("duplicate subnet IP %s", ip)
}
seen[ip] = true
if !strings.HasPrefix(ip, sn.Prefix+".") {
t.Fatalf("IP %q not within prefix %q", ip, sn.Prefix)
}
}
}