orama/pkg/namespace/webrtc_port_allocator_test.go
anonpenguin23 8ee606bfb1 feat: implement SFU and TURN server functionality
- Add signaling package with message types and structures for SFU communication.
- Implement client and server message serialization/deserialization tests.
- Enhance systemd manager to handle SFU and TURN services, including start/stop logic.
- Create TURN server configuration and main server logic with HMAC-SHA1 authentication.
- Add tests for TURN server credential generation and validation.
- Define systemd service files for SFU and TURN services.
2026-02-21 11:17:13 +02:00

338 lines
11 KiB
Go

package namespace
import (
"context"
"strings"
"testing"
"go.uber.org/zap"
)
func TestWebRTCPortConstants_NoOverlap(t *testing.T) {
// Verify WebRTC port ranges don't overlap with core namespace ports (10000-10099)
ranges := []struct {
name string
start int
end int
}{
{"core namespace", NamespacePortRangeStart, NamespacePortRangeEnd},
{"SFU media", SFUMediaPortRangeStart, SFUMediaPortRangeEnd},
{"SFU signaling", SFUSignalingPortRangeStart, SFUSignalingPortRangeEnd},
{"TURN relay", TURNRelayPortRangeStart, TURNRelayPortRangeEnd},
}
for i := 0; i < len(ranges); i++ {
for j := i + 1; j < len(ranges); j++ {
a, b := ranges[i], ranges[j]
if a.start <= b.end && b.start <= a.end {
t.Errorf("Range overlap: %s (%d-%d) overlaps with %s (%d-%d)",
a.name, a.start, a.end, b.name, b.start, b.end)
}
}
}
}
func TestWebRTCPortConstants_Capacity(t *testing.T) {
// SFU media: (29999-20000+1)/500 = 20 namespaces per node
sfuMediaCapacity := (SFUMediaPortRangeEnd - SFUMediaPortRangeStart + 1) / SFUMediaPortsPerNamespace
if sfuMediaCapacity < 20 {
t.Errorf("SFU media capacity = %d, want >= 20", sfuMediaCapacity)
}
// SFU signaling: 30099-30000+1 = 100 ports → 100 namespaces per node
sfuSignalingCapacity := SFUSignalingPortRangeEnd - SFUSignalingPortRangeStart + 1
if sfuSignalingCapacity < 20 {
t.Errorf("SFU signaling capacity = %d, want >= 20", sfuSignalingCapacity)
}
// TURN relay: (65535-49152+1)/800 = 20 namespaces per node
turnRelayCapacity := (TURNRelayPortRangeEnd - TURNRelayPortRangeStart + 1) / TURNRelayPortsPerNamespace
if turnRelayCapacity < 20 {
t.Errorf("TURN relay capacity = %d, want >= 20", turnRelayCapacity)
}
}
func TestWebRTCPortConstants_Values(t *testing.T) {
if SFUMediaPortRangeStart != 20000 {
t.Errorf("SFUMediaPortRangeStart = %d, want 20000", SFUMediaPortRangeStart)
}
if SFUMediaPortRangeEnd != 29999 {
t.Errorf("SFUMediaPortRangeEnd = %d, want 29999", SFUMediaPortRangeEnd)
}
if SFUMediaPortsPerNamespace != 500 {
t.Errorf("SFUMediaPortsPerNamespace = %d, want 500", SFUMediaPortsPerNamespace)
}
if SFUSignalingPortRangeStart != 30000 {
t.Errorf("SFUSignalingPortRangeStart = %d, want 30000", SFUSignalingPortRangeStart)
}
if TURNRelayPortRangeStart != 49152 {
t.Errorf("TURNRelayPortRangeStart = %d, want 49152", TURNRelayPortRangeStart)
}
if TURNRelayPortsPerNamespace != 800 {
t.Errorf("TURNRelayPortsPerNamespace = %d, want 800", TURNRelayPortsPerNamespace)
}
if TURNDefaultPort != 3478 {
t.Errorf("TURNDefaultPort = %d, want 3478", TURNDefaultPort)
}
if DefaultSFUNodeCount != 3 {
t.Errorf("DefaultSFUNodeCount = %d, want 3", DefaultSFUNodeCount)
}
if DefaultTURNNodeCount != 2 {
t.Errorf("DefaultTURNNodeCount = %d, want 2", DefaultTURNNodeCount)
}
}
func TestNewWebRTCPortAllocator(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
if allocator == nil {
t.Fatal("NewWebRTCPortAllocator returned nil")
}
if allocator.db != mockDB {
t.Error("allocator.db not set correctly")
}
}
func TestWebRTCPortAllocator_AllocateSFUPorts(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
block, err := allocator.AllocateSFUPorts(context.Background(), "node-1", "cluster-1")
if err != nil {
t.Fatalf("AllocateSFUPorts failed: %v", err)
}
if block == nil {
t.Fatal("AllocateSFUPorts returned nil block")
}
if block.ServiceType != "sfu" {
t.Errorf("ServiceType = %q, want %q", block.ServiceType, "sfu")
}
if block.NodeID != "node-1" {
t.Errorf("NodeID = %q, want %q", block.NodeID, "node-1")
}
if block.NamespaceClusterID != "cluster-1" {
t.Errorf("NamespaceClusterID = %q, want %q", block.NamespaceClusterID, "cluster-1")
}
// First allocation should get the first port in each range
if block.SFUSignalingPort != SFUSignalingPortRangeStart {
t.Errorf("SFUSignalingPort = %d, want %d", block.SFUSignalingPort, SFUSignalingPortRangeStart)
}
if block.SFUMediaPortStart != SFUMediaPortRangeStart {
t.Errorf("SFUMediaPortStart = %d, want %d", block.SFUMediaPortStart, SFUMediaPortRangeStart)
}
if block.SFUMediaPortEnd != SFUMediaPortRangeStart+SFUMediaPortsPerNamespace-1 {
t.Errorf("SFUMediaPortEnd = %d, want %d", block.SFUMediaPortEnd, SFUMediaPortRangeStart+SFUMediaPortsPerNamespace-1)
}
// TURN fields should be zero for SFU allocation
if block.TURNListenPort != 0 {
t.Errorf("TURNListenPort = %d, want 0 for SFU allocation", block.TURNListenPort)
}
if block.TURNRelayPortStart != 0 {
t.Errorf("TURNRelayPortStart = %d, want 0 for SFU allocation", block.TURNRelayPortStart)
}
// Verify INSERT was called
hasInsert := false
for _, call := range mockDB.execCalls {
if strings.Contains(call.Query, "INSERT INTO webrtc_port_allocations") {
hasInsert = true
break
}
}
if !hasInsert {
t.Error("expected INSERT INTO webrtc_port_allocations to be called")
}
}
func TestWebRTCPortAllocator_AllocateTURNPorts(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
block, err := allocator.AllocateTURNPorts(context.Background(), "node-1", "cluster-1")
if err != nil {
t.Fatalf("AllocateTURNPorts failed: %v", err)
}
if block == nil {
t.Fatal("AllocateTURNPorts returned nil block")
}
if block.ServiceType != "turn" {
t.Errorf("ServiceType = %q, want %q", block.ServiceType, "turn")
}
if block.TURNListenPort != TURNDefaultPort {
t.Errorf("TURNListenPort = %d, want %d", block.TURNListenPort, TURNDefaultPort)
}
if block.TURNTLSPort != TURNTLSPort {
t.Errorf("TURNTLSPort = %d, want %d", block.TURNTLSPort, TURNTLSPort)
}
if block.TURNRelayPortStart != TURNRelayPortRangeStart {
t.Errorf("TURNRelayPortStart = %d, want %d", block.TURNRelayPortStart, TURNRelayPortRangeStart)
}
if block.TURNRelayPortEnd != TURNRelayPortRangeStart+TURNRelayPortsPerNamespace-1 {
t.Errorf("TURNRelayPortEnd = %d, want %d", block.TURNRelayPortEnd, TURNRelayPortRangeStart+TURNRelayPortsPerNamespace-1)
}
// SFU fields should be zero for TURN allocation
if block.SFUSignalingPort != 0 {
t.Errorf("SFUSignalingPort = %d, want 0 for TURN allocation", block.SFUSignalingPort)
}
}
func TestWebRTCPortAllocator_DeallocateAll(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
err := allocator.DeallocateAll(context.Background(), "cluster-1")
if err != nil {
t.Fatalf("DeallocateAll failed: %v", err)
}
// Verify DELETE was called with correct cluster ID
hasDelete := false
for _, call := range mockDB.execCalls {
if strings.Contains(call.Query, "DELETE FROM webrtc_port_allocations") &&
strings.Contains(call.Query, "namespace_cluster_id") {
hasDelete = true
if len(call.Args) < 1 || call.Args[0] != "cluster-1" {
t.Errorf("DELETE called with wrong cluster ID: %v", call.Args)
}
}
}
if !hasDelete {
t.Error("expected DELETE FROM webrtc_port_allocations to be called")
}
}
func TestWebRTCPortAllocator_DeallocateByNode(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
err := allocator.DeallocateByNode(context.Background(), "cluster-1", "node-1", "sfu")
if err != nil {
t.Fatalf("DeallocateByNode failed: %v", err)
}
// Verify DELETE was called with correct parameters
hasDelete := false
for _, call := range mockDB.execCalls {
if strings.Contains(call.Query, "DELETE FROM webrtc_port_allocations") &&
strings.Contains(call.Query, "service_type") {
hasDelete = true
if len(call.Args) != 3 {
t.Fatalf("DELETE called with %d args, want 3", len(call.Args))
}
if call.Args[0] != "cluster-1" {
t.Errorf("arg[0] = %v, want cluster-1", call.Args[0])
}
if call.Args[1] != "node-1" {
t.Errorf("arg[1] = %v, want node-1", call.Args[1])
}
if call.Args[2] != "sfu" {
t.Errorf("arg[2] = %v, want sfu", call.Args[2])
}
}
}
if !hasDelete {
t.Error("expected DELETE FROM webrtc_port_allocations to be called")
}
}
func TestWebRTCPortAllocator_NodeHasTURN(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
// Mock query returns empty results → no TURN on node
hasTURN, err := allocator.NodeHasTURN(context.Background(), "node-1")
if err != nil {
t.Fatalf("NodeHasTURN failed: %v", err)
}
if hasTURN {
t.Error("expected NodeHasTURN = false for node with no allocations")
}
}
func TestWebRTCPortAllocator_GetSFUPorts_NoAllocation(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
block, err := allocator.GetSFUPorts(context.Background(), "cluster-1", "node-1")
if err != nil {
t.Fatalf("GetSFUPorts failed: %v", err)
}
if block != nil {
t.Error("expected nil block when no allocation exists")
}
}
func TestWebRTCPortAllocator_GetTURNPorts_NoAllocation(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
block, err := allocator.GetTURNPorts(context.Background(), "cluster-1", "node-1")
if err != nil {
t.Fatalf("GetTURNPorts failed: %v", err)
}
if block != nil {
t.Error("expected nil block when no allocation exists")
}
}
func TestWebRTCPortAllocator_GetAllPorts_Empty(t *testing.T) {
mockDB := newMockRQLiteClient()
allocator := NewWebRTCPortAllocator(mockDB, testLogger())
blocks, err := allocator.GetAllPorts(context.Background(), "cluster-1")
if err != nil {
t.Fatalf("GetAllPorts failed: %v", err)
}
if len(blocks) != 0 {
t.Errorf("expected 0 blocks, got %d", len(blocks))
}
}
func TestWebRTCPortBlock_SFUFields(t *testing.T) {
block := &WebRTCPortBlock{
ID: "test-id",
NodeID: "node-1",
NamespaceClusterID: "cluster-1",
ServiceType: "sfu",
SFUSignalingPort: 30000,
SFUMediaPortStart: 20000,
SFUMediaPortEnd: 20499,
}
mediaRange := block.SFUMediaPortEnd - block.SFUMediaPortStart + 1
if mediaRange != SFUMediaPortsPerNamespace {
t.Errorf("SFU media range = %d, want %d", mediaRange, SFUMediaPortsPerNamespace)
}
}
func TestWebRTCPortBlock_TURNFields(t *testing.T) {
block := &WebRTCPortBlock{
ID: "test-id",
NodeID: "node-1",
NamespaceClusterID: "cluster-1",
ServiceType: "turn",
TURNListenPort: 3478,
TURNTLSPort: 443,
TURNRelayPortStart: 49152,
TURNRelayPortEnd: 49951,
}
relayRange := block.TURNRelayPortEnd - block.TURNRelayPortStart + 1
if relayRange != TURNRelayPortsPerNamespace {
t.Errorf("TURN relay range = %d, want %d", relayRange, TURNRelayPortsPerNamespace)
}
}
// testLogger returns a no-op logger for tests
func testLogger() *zap.Logger {
return zap.NewNop()
}