- Remove live_routes table from SQL schema and all related indexes - Create new internal/routingtable package with thread-safe RoutingTable - Implement RouteKey-based indexing with secondary indexes for efficient lookups - Add RoutingTableHandler to manage in-memory routes separately from database - Update DatabaseHandler to only handle persistent database operations - Wire up RoutingTable through fx dependency injection - Update server to get live route count from routing table instead of database - Remove LiveRoutes field from database.Stats struct - Update tests to work with new architecture
220 lines
4.8 KiB
Go
220 lines
4.8 KiB
Go
package routingtable
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestRoutingTable(t *testing.T) {
|
|
rt := New()
|
|
|
|
// Test data
|
|
prefixID1 := uuid.New()
|
|
prefixID2 := uuid.New()
|
|
originASNID1 := uuid.New()
|
|
originASNID2 := uuid.New()
|
|
|
|
route1 := &Route{
|
|
PrefixID: prefixID1,
|
|
Prefix: "10.0.0.0/8",
|
|
OriginASNID: originASNID1,
|
|
OriginASN: 64512,
|
|
PeerASN: 64513,
|
|
ASPath: []int{64513, 64512},
|
|
NextHop: "192.168.1.1",
|
|
AnnouncedAt: time.Now(),
|
|
}
|
|
|
|
route2 := &Route{
|
|
PrefixID: prefixID2,
|
|
Prefix: "192.168.0.0/16",
|
|
OriginASNID: originASNID2,
|
|
OriginASN: 64514,
|
|
PeerASN: 64513,
|
|
ASPath: []int{64513, 64514},
|
|
NextHop: "192.168.1.1",
|
|
AnnouncedAt: time.Now(),
|
|
}
|
|
|
|
// Test AddRoute
|
|
rt.AddRoute(route1)
|
|
rt.AddRoute(route2)
|
|
|
|
if rt.Size() != 2 {
|
|
t.Errorf("Expected 2 routes, got %d", rt.Size())
|
|
}
|
|
|
|
// Test GetRoute
|
|
retrievedRoute, exists := rt.GetRoute(prefixID1, originASNID1, 64513)
|
|
if !exists {
|
|
t.Error("Route 1 should exist")
|
|
}
|
|
if retrievedRoute.Prefix != "10.0.0.0/8" {
|
|
t.Errorf("Expected prefix 10.0.0.0/8, got %s", retrievedRoute.Prefix)
|
|
}
|
|
|
|
// Test GetRoutesByPrefix
|
|
prefixRoutes := rt.GetRoutesByPrefix(prefixID1)
|
|
if len(prefixRoutes) != 1 {
|
|
t.Errorf("Expected 1 route for prefix, got %d", len(prefixRoutes))
|
|
}
|
|
|
|
// Test GetRoutesByPeerASN
|
|
peerRoutes := rt.GetRoutesByPeerASN(64513)
|
|
if len(peerRoutes) != 2 {
|
|
t.Errorf("Expected 2 routes from peer 64513, got %d", len(peerRoutes))
|
|
}
|
|
|
|
// Test RemoveRoute
|
|
removed := rt.RemoveRoute(prefixID1, originASNID1, 64513)
|
|
if !removed {
|
|
t.Error("Route should have been removed")
|
|
}
|
|
if rt.Size() != 1 {
|
|
t.Errorf("Expected 1 route after removal, got %d", rt.Size())
|
|
}
|
|
|
|
// Test WithdrawRoutesByPrefixAndPeer
|
|
// Add the route back first
|
|
rt.AddRoute(route1)
|
|
|
|
// Add another route for the same prefix from the same peer
|
|
route3 := &Route{
|
|
PrefixID: prefixID1,
|
|
Prefix: "10.0.0.0/8",
|
|
OriginASNID: originASNID2, // Different origin
|
|
OriginASN: 64515,
|
|
PeerASN: 64513,
|
|
ASPath: []int{64513, 64515},
|
|
NextHop: "192.168.1.1",
|
|
AnnouncedAt: time.Now(),
|
|
}
|
|
rt.AddRoute(route3)
|
|
|
|
count := rt.WithdrawRoutesByPrefixAndPeer(prefixID1, 64513)
|
|
if count != 2 {
|
|
t.Errorf("Expected to withdraw 2 routes, withdrew %d", count)
|
|
}
|
|
|
|
// Should only have route2 left
|
|
if rt.Size() != 1 {
|
|
t.Errorf("Expected 1 route after withdrawal, got %d", rt.Size())
|
|
}
|
|
|
|
// Test Stats
|
|
stats := rt.Stats()
|
|
if stats["total_routes"] != 1 {
|
|
t.Errorf("Expected 1 total route in stats, got %d", stats["total_routes"])
|
|
}
|
|
|
|
// Test Clear
|
|
rt.Clear()
|
|
if rt.Size() != 0 {
|
|
t.Errorf("Expected 0 routes after clear, got %d", rt.Size())
|
|
}
|
|
}
|
|
|
|
func TestRoutingTableConcurrency(t *testing.T) {
|
|
rt := New()
|
|
|
|
// Test concurrent access
|
|
var wg sync.WaitGroup
|
|
numGoroutines := 10
|
|
numOperations := 100
|
|
|
|
// Start multiple goroutines that add/remove routes
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < numOperations; j++ {
|
|
prefixID := uuid.New()
|
|
originASNID := uuid.New()
|
|
|
|
route := &Route{
|
|
PrefixID: prefixID,
|
|
Prefix: "10.0.0.0/8",
|
|
OriginASNID: originASNID,
|
|
OriginASN: 64512 + id,
|
|
PeerASN: 64500,
|
|
ASPath: []int{64500, 64512 + id},
|
|
NextHop: "192.168.1.1",
|
|
AnnouncedAt: time.Now(),
|
|
}
|
|
|
|
// Add route
|
|
rt.AddRoute(route)
|
|
|
|
// Try to get it
|
|
_, _ = rt.GetRoute(prefixID, originASNID, 64500)
|
|
|
|
// Get stats
|
|
_ = rt.Stats()
|
|
|
|
// Remove it
|
|
rt.RemoveRoute(prefixID, originASNID, 64500)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Table should be empty after all operations
|
|
if rt.Size() != 0 {
|
|
t.Errorf("Expected empty table after concurrent operations, got %d routes", rt.Size())
|
|
}
|
|
}
|
|
|
|
func TestRouteUpdate(t *testing.T) {
|
|
rt := New()
|
|
|
|
prefixID := uuid.New()
|
|
originASNID := uuid.New()
|
|
|
|
route1 := &Route{
|
|
PrefixID: prefixID,
|
|
Prefix: "10.0.0.0/8",
|
|
OriginASNID: originASNID,
|
|
OriginASN: 64512,
|
|
PeerASN: 64513,
|
|
ASPath: []int{64513, 64512},
|
|
NextHop: "192.168.1.1",
|
|
AnnouncedAt: time.Now(),
|
|
}
|
|
|
|
// Add initial route
|
|
rt.AddRoute(route1)
|
|
|
|
// Update the same route with new next hop
|
|
route2 := &Route{
|
|
PrefixID: prefixID,
|
|
Prefix: "10.0.0.0/8",
|
|
OriginASNID: originASNID,
|
|
OriginASN: 64512,
|
|
PeerASN: 64513,
|
|
ASPath: []int{64513, 64512},
|
|
NextHop: "192.168.1.2", // Changed
|
|
AnnouncedAt: time.Now().Add(1 * time.Minute),
|
|
}
|
|
|
|
rt.AddRoute(route2)
|
|
|
|
// Should still have only 1 route
|
|
if rt.Size() != 1 {
|
|
t.Errorf("Expected 1 route after update, got %d", rt.Size())
|
|
}
|
|
|
|
// Check that the route was updated
|
|
retrievedRoute, exists := rt.GetRoute(prefixID, originASNID, 64513)
|
|
if !exists {
|
|
t.Error("Route should exist after update")
|
|
}
|
|
if retrievedRoute.NextHop != "192.168.1.2" {
|
|
t.Errorf("Expected updated next hop 192.168.1.2, got %s", retrievedRoute.NextHop)
|
|
}
|
|
}
|