network/pkg/namespace/port_allocator_test.go

311 lines
8.5 KiB
Go

package namespace
import (
"context"
"database/sql"
"errors"
"testing"
"time"
"github.com/DeBrosOfficial/network/pkg/rqlite"
"go.uber.org/zap"
)
// mockResult implements sql.Result
type mockResult struct {
lastInsertID int64
rowsAffected int64
}
func (m mockResult) LastInsertId() (int64, error) { return m.lastInsertID, nil }
func (m mockResult) RowsAffected() (int64, error) { return m.rowsAffected, nil }
// mockRQLiteClient implements rqlite.Client for testing
type mockRQLiteClient struct {
queryResults map[string]interface{}
execResults map[string]error
queryCalls []mockQueryCall
execCalls []mockExecCall
}
type mockQueryCall struct {
Query string
Args []interface{}
}
type mockExecCall struct {
Query string
Args []interface{}
}
func newMockRQLiteClient() *mockRQLiteClient {
return &mockRQLiteClient{
queryResults: make(map[string]interface{}),
execResults: make(map[string]error),
queryCalls: make([]mockQueryCall, 0),
execCalls: make([]mockExecCall, 0),
}
}
func (m *mockRQLiteClient) Query(ctx context.Context, dest any, query string, args ...any) error {
ifaceArgs := make([]interface{}, len(args))
for i, a := range args {
ifaceArgs[i] = a
}
m.queryCalls = append(m.queryCalls, mockQueryCall{Query: query, Args: ifaceArgs})
return nil
}
func (m *mockRQLiteClient) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
ifaceArgs := make([]interface{}, len(args))
for i, a := range args {
ifaceArgs[i] = a
}
m.execCalls = append(m.execCalls, mockExecCall{Query: query, Args: ifaceArgs})
if err, ok := m.execResults[query]; ok {
return nil, err
}
return mockResult{rowsAffected: 1}, nil
}
func (m *mockRQLiteClient) FindBy(ctx context.Context, dest any, table string, criteria map[string]any, opts ...rqlite.FindOption) error {
return nil
}
func (m *mockRQLiteClient) FindOneBy(ctx context.Context, dest any, table string, criteria map[string]any, opts ...rqlite.FindOption) error {
return nil
}
func (m *mockRQLiteClient) Save(ctx context.Context, entity any) error {
return nil
}
func (m *mockRQLiteClient) Remove(ctx context.Context, entity any) error {
return nil
}
func (m *mockRQLiteClient) Repository(table string) any {
return nil
}
func (m *mockRQLiteClient) CreateQueryBuilder(table string) *rqlite.QueryBuilder {
return nil
}
func (m *mockRQLiteClient) Tx(ctx context.Context, fn func(tx rqlite.Tx) error) error {
return nil
}
// Ensure mockRQLiteClient implements rqlite.Client
var _ rqlite.Client = (*mockRQLiteClient)(nil)
func TestPortBlock_PortAssignment(t *testing.T) {
// Test that port block correctly assigns ports
block := &PortBlock{
ID: "test-id",
NodeID: "node-1",
NamespaceClusterID: "cluster-1",
PortStart: 10000,
PortEnd: 10004,
RQLiteHTTPPort: 10000,
RQLiteRaftPort: 10001,
OlricHTTPPort: 10002,
OlricMemberlistPort: 10003,
GatewayHTTPPort: 10004,
AllocatedAt: time.Now(),
}
// Verify port assignments
if block.RQLiteHTTPPort != block.PortStart+0 {
t.Errorf("RQLiteHTTPPort = %d, want %d", block.RQLiteHTTPPort, block.PortStart+0)
}
if block.RQLiteRaftPort != block.PortStart+1 {
t.Errorf("RQLiteRaftPort = %d, want %d", block.RQLiteRaftPort, block.PortStart+1)
}
if block.OlricHTTPPort != block.PortStart+2 {
t.Errorf("OlricHTTPPort = %d, want %d", block.OlricHTTPPort, block.PortStart+2)
}
if block.OlricMemberlistPort != block.PortStart+3 {
t.Errorf("OlricMemberlistPort = %d, want %d", block.OlricMemberlistPort, block.PortStart+3)
}
if block.GatewayHTTPPort != block.PortStart+4 {
t.Errorf("GatewayHTTPPort = %d, want %d", block.GatewayHTTPPort, block.PortStart+4)
}
}
func TestPortConstants(t *testing.T) {
// Verify constants are correctly defined
if NamespacePortRangeStart != 10000 {
t.Errorf("NamespacePortRangeStart = %d, want 10000", NamespacePortRangeStart)
}
if NamespacePortRangeEnd != 10099 {
t.Errorf("NamespacePortRangeEnd = %d, want 10099", NamespacePortRangeEnd)
}
if PortsPerNamespace != 5 {
t.Errorf("PortsPerNamespace = %d, want 5", PortsPerNamespace)
}
// Verify max namespaces calculation: (10099 - 10000 + 1) / 5 = 100 / 5 = 20
expectedMax := (NamespacePortRangeEnd - NamespacePortRangeStart + 1) / PortsPerNamespace
if MaxNamespacesPerNode != expectedMax {
t.Errorf("MaxNamespacesPerNode = %d, want %d", MaxNamespacesPerNode, expectedMax)
}
if MaxNamespacesPerNode != 20 {
t.Errorf("MaxNamespacesPerNode = %d, want 20", MaxNamespacesPerNode)
}
}
func TestPortRangeCapacity(t *testing.T) {
// Test that 20 namespaces fit exactly in the port range
usedPorts := MaxNamespacesPerNode * PortsPerNamespace
availablePorts := NamespacePortRangeEnd - NamespacePortRangeStart + 1
if usedPorts > availablePorts {
t.Errorf("Port range overflow: %d ports needed for %d namespaces, but only %d available",
usedPorts, MaxNamespacesPerNode, availablePorts)
}
// Verify no wasted ports
if usedPorts != availablePorts {
t.Logf("Note: %d ports unused in range", availablePorts-usedPorts)
}
}
func TestPortBlockAllocation_SequentialBlocks(t *testing.T) {
// Verify that sequential port blocks don't overlap
blocks := make([]*PortBlock, MaxNamespacesPerNode)
for i := 0; i < MaxNamespacesPerNode; i++ {
portStart := NamespacePortRangeStart + (i * PortsPerNamespace)
blocks[i] = &PortBlock{
PortStart: portStart,
PortEnd: portStart + PortsPerNamespace - 1,
RQLiteHTTPPort: portStart + 0,
RQLiteRaftPort: portStart + 1,
OlricHTTPPort: portStart + 2,
OlricMemberlistPort: portStart + 3,
GatewayHTTPPort: portStart + 4,
}
}
// Verify no overlap between consecutive blocks
for i := 0; i < len(blocks)-1; i++ {
if blocks[i].PortEnd >= blocks[i+1].PortStart {
t.Errorf("Block %d (end=%d) overlaps with block %d (start=%d)",
i, blocks[i].PortEnd, i+1, blocks[i+1].PortStart)
}
}
// Verify last block doesn't exceed range
lastBlock := blocks[len(blocks)-1]
if lastBlock.PortEnd > NamespacePortRangeEnd {
t.Errorf("Last block exceeds port range: end=%d, max=%d",
lastBlock.PortEnd, NamespacePortRangeEnd)
}
}
func TestIsConflictError(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "UNIQUE constraint error",
err: errors.New("UNIQUE constraint failed"),
expected: true,
},
{
name: "constraint violation",
err: errors.New("constraint violation"),
expected: true,
},
{
name: "conflict error",
err: errors.New("conflict detected"),
expected: true,
},
{
name: "regular error",
err: errors.New("connection timeout"),
expected: false,
},
{
name: "empty error",
err: errors.New(""),
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isConflictError(tt.err)
if result != tt.expected {
t.Errorf("isConflictError(%v) = %v, want %v", tt.err, result, tt.expected)
}
})
}
}
func TestContains(t *testing.T) {
tests := []struct {
s string
substr string
expected bool
}{
{"hello world", "world", true},
{"hello world", "hello", true},
{"hello world", "xyz", false},
{"", "", true},
{"hello", "", true},
{"", "hello", false},
{"UNIQUE constraint", "UNIQUE", true},
}
for _, tt := range tests {
t.Run(tt.s+"_"+tt.substr, func(t *testing.T) {
result := contains(tt.s, tt.substr)
if result != tt.expected {
t.Errorf("contains(%q, %q) = %v, want %v", tt.s, tt.substr, result, tt.expected)
}
})
}
}
func TestNewNamespacePortAllocator(t *testing.T) {
mockDB := newMockRQLiteClient()
logger := zap.NewNop()
allocator := NewNamespacePortAllocator(mockDB, logger)
if allocator == nil {
t.Fatal("NewNamespacePortAllocator returned nil")
}
}
func TestDefaultClusterSizes(t *testing.T) {
// Verify default cluster size constants
if DefaultRQLiteNodeCount != 3 {
t.Errorf("DefaultRQLiteNodeCount = %d, want 3", DefaultRQLiteNodeCount)
}
if DefaultOlricNodeCount != 3 {
t.Errorf("DefaultOlricNodeCount = %d, want 3", DefaultOlricNodeCount)
}
if DefaultGatewayNodeCount != 3 {
t.Errorf("DefaultGatewayNodeCount = %d, want 3", DefaultGatewayNodeCount)
}
// Public namespace should have larger clusters
if PublicRQLiteNodeCount != 5 {
t.Errorf("PublicRQLiteNodeCount = %d, want 5", PublicRQLiteNodeCount)
}
if PublicOlricNodeCount != 5 {
t.Errorf("PublicOlricNodeCount = %d, want 5", PublicOlricNodeCount)
}
}