| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- package sdwan
- import (
- "net"
- "path/filepath"
- "testing"
- "time"
- "github.com/vishvananda/netlink"
- )
- func TestSDWANManagerBasics(t *testing.T) {
- tmpDir := t.TempDir()
- storePath := filepath.Join(tmpDir, "sdwan.json")
- mgr := NewManager(storePath)
- defer mgr.Stop()
- if mgr == nil {
- t.Fatal("expected non-nil manager")
- }
- // 1. Create SD-WAN group
- group := &SDWANGroup{
- Name: "sdwan0",
- Mode: ModeWeighted,
- ProbeInterval: 3,
- AutoNAT: true,
- Comment: "Primary Internet SD-WAN",
- }
- if err := mgr.Save(group); err != nil {
- t.Fatalf("failed to save group: %v", err)
- }
- // Verify group is retrievable by Name and ID
- got, ok := mgr.Get("sdwan0")
- if !ok || got == nil {
- t.Fatal("expected to find sdwan0 by name")
- }
- if got.ID == "" {
- t.Fatal("expected auto-generated group ID")
- }
- // 2. Add members
- mem1 := &SDWANMember{
- Interface: "eth0",
- Gateway: "192.168.1.1",
- Weight: 70,
- TargetProbe: "1.1.1.1",
- }
- if err := mgr.AddMember("sdwan0", mem1); err != nil {
- t.Fatalf("failed adding member 1: %v", err)
- }
- mem2 := &SDWANMember{
- Interface: "eth1",
- Gateway: "192.168.2.1",
- Weight: 30,
- TargetProbe: "8.8.8.8",
- }
- if err := mgr.AddMember("sdwan0", mem2); err != nil {
- t.Fatalf("failed adding member 2: %v", err)
- }
- // Test duplicate member rejection
- if err := mgr.AddMember("sdwan0", &SDWANMember{Interface: "eth0"}); err == nil {
- t.Fatal("expected error on duplicate member interface")
- }
- // 3. Test Interface Resolution for Firewall
- resolved := mgr.ResolveGroupInterfaces("sdwan0")
- if len(resolved) != 2 || resolved[0] != "eth0" || resolved[1] != "eth1" {
- t.Fatalf("expected [eth0, eth1], got %v", resolved)
- }
- allGroups := mgr.ResolveAllSDWANGroups()
- if len(allGroups["sdwan0"]) != 2 {
- t.Fatalf("expected sdwan0 in ResolveAllSDWANGroups, got %v", allGroups)
- }
- // 4. Update Member
- mem1.Weight = 80
- if err := mgr.UpdateMember("sdwan0", mem1); err != nil {
- t.Fatalf("failed updating member: %v", err)
- }
- updated, _ := mgr.Get("sdwan0")
- if updated.Members[0].Weight != 80 {
- t.Fatalf("expected weight 80, got %d", updated.Members[0].Weight)
- }
- // 5. Telemetry Summary
- summary := mgr.GetSummary()
- if summary.TotalGroups != 1 || summary.ActiveGroups != 1 || summary.TotalMembers != 2 {
- t.Fatalf("unexpected summary: %+v", summary)
- }
- // 6. Delete Member
- if err := mgr.DeleteMember("sdwan0", "eth1"); err != nil {
- t.Fatalf("failed deleting member: %v", err)
- }
- resolvedAfterDelete := mgr.ResolveGroupInterfaces("sdwan0")
- if len(resolvedAfterDelete) != 1 || resolvedAfterDelete[0] != "eth0" {
- t.Fatalf("expected [eth0], got %v", resolvedAfterDelete)
- }
- // 7. Delete Group
- if err := mgr.Delete("sdwan0"); err != nil {
- t.Fatalf("failed deleting group: %v", err)
- }
- if len(mgr.List()) != 0 {
- t.Fatal("expected 0 groups after delete")
- }
- }
- func TestHealthMetricsCalculation(t *testing.T) {
- monitor := NewHealthMonitor(nil)
- mem := &SDWANMember{
- Interface: "eth0",
- MaxLatency: 100,
- MaxLoss: 10,
- }
- samples := []probeSample{
- {latency: 20.0, success: true, t: time.Now()},
- {latency: 25.0, success: true, t: time.Now()},
- {latency: 30.0, success: true, t: time.Now()},
- {latency: 0.0, success: false, t: time.Now()},
- }
- monitor.calculateMetrics(mem, samples)
- // 1 failure out of 4 = 25% loss
- if mem.PacketLoss != 25.0 {
- t.Errorf("expected 25.0%% loss, got %.1f", mem.PacketLoss)
- }
- // Average of 20, 25, 30 is 25.0 ms
- if mem.LiveLatency != 25.0 {
- t.Errorf("expected 25.0ms latency, got %.1f", mem.LiveLatency)
- }
- if mem.Jitter <= 0 {
- t.Errorf("expected positive jitter, got %.1f", mem.Jitter)
- }
- }
- func TestHealthHysteresisAndThresholds(t *testing.T) {
- mem := &SDWANMember{
- Interface: "eth0",
- MaxLatency: 250,
- MaxLoss: 20,
- Status: StatusHealthy,
- }
- // 1-4 fails: should NOT transition to StatusDown
- for i := 1; i <= 4; i++ {
- mem.ConsecutiveFails = i
- mem.ConsecutiveSuccesses = 0
- if mem.ConsecutiveFails >= 5 {
- mem.Status = StatusDown
- }
- if mem.Status == StatusDown {
- t.Fatalf("member became DOWN prematurely at %d fails", i)
- }
- }
- // 5th fail: transitions to StatusDown
- mem.ConsecutiveFails = 5
- if mem.ConsecutiveFails >= 5 {
- mem.Status = StatusDown
- }
- if mem.Status != StatusDown {
- t.Fatalf("expected StatusDown after 5 fails, got %s", mem.Status)
- }
- // 1st recovery success: hysteresis must keep it StatusDown
- prevStatus := mem.Status
- mem.ConsecutiveSuccesses = 1
- mem.ConsecutiveFails = 0
- if prevStatus == StatusDown && mem.ConsecutiveSuccesses < 2 {
- mem.Status = StatusDown
- }
- if mem.Status != StatusDown {
- t.Fatalf("expected StatusDown during 1st success cycle (hysteresis), got %s", mem.Status)
- }
- // 2nd recovery success: now allowed to recover
- mem.ConsecutiveSuccesses = 2
- if prevStatus == StatusDown && mem.ConsecutiveSuccesses < 2 {
- mem.Status = StatusDown
- } else {
- mem.Status = StatusHealthy
- }
- if mem.Status != StatusHealthy {
- t.Fatalf("expected StatusHealthy after 2 consecutive successes, got %s", mem.Status)
- }
- }
- func TestSDWANVirtualDeviceLifecycle(t *testing.T) {
- tmpDir := t.TempDir()
- storePath := filepath.Join(tmpDir, "sdwan.json")
- mgr := NewManager(storePath)
- defer mgr.Stop()
- group := &SDWANGroup{
- Name: "sdwantest0",
- Mode: ModeFailover,
- AutoNAT: false,
- Members: []*SDWANMember{
- {Interface: "lo", Gateway: "127.0.0.1", Weight: 100},
- },
- }
- if err := mgr.Save(group); err != nil {
- t.Fatalf("failed saving group: %v", err)
- }
- // Verify virtual device exists in kernel
- link, err := netlink.LinkByName("sdwantest0")
- if err != nil {
- t.Fatalf("expected sdwantest0 virtual device to be created in kernel: %v", err)
- }
- if link.Attrs().Flags&net.FlagUp == 0 {
- t.Fatalf("expected sdwantest0 to be UP")
- }
- // Delete group and verify virtual device is removed
- if err := mgr.Delete("sdwantest0"); err != nil {
- t.Fatalf("failed to delete group: %v", err)
- }
- if _, err := netlink.LinkByName("sdwantest0"); err == nil {
- t.Fatalf("expected sdwantest0 virtual device to be removed from kernel after group delete")
- }
- }
|