| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- package dynamicrouting
- import (
- "encoding/json"
- "os"
- "os/exec"
- "strings"
- "testing"
- )
- func TestAllProtocolsBirdConfigGeneration(t *testing.T) {
- mgr := &Manager{
- config: DynamicConfig{
- BGPInstance: BGPInstance{
- Enabled: true,
- AS: 65001,
- RouterID: "10.42.62.254",
- },
- BGPPeers: []BGPPeer{
- {
- ID: "peer-1",
- Name: "Uplink",
- RemoteAddress: "10.42.36.253",
- RemoteAS: 65002,
- UseBFD: true,
- },
- },
- OSPFInstances: []OSPFInstance{
- {
- ID: "ospf-1",
- Name: "Default",
- Version: 2,
- RouterID: "10.42.62.254",
- Enabled: true,
- },
- },
- OSPFAreas: []OSPFArea{
- {
- ID: "area-0",
- Name: "Default",
- AreaID: "0.0.0.0",
- AreaType: "default",
- },
- },
- OSPFInterfaces: []OSPFInterface{
- {
- ID: "ospf-if-1",
- Interfaces: "eth0",
- Area: "Default",
- Cost: 10,
- UseBFD: true,
- },
- },
- BFDInstance: BFDInstance{
- Enabled: true,
- },
- BFDInterfaces: []BFDInterface{
- {
- ID: "bfd-if-1",
- Interface: "eth0",
- MinRxInterval: 100,
- MinTxInterval: 100,
- Multiplier: 3,
- },
- },
- BFDNeighbors: []BFDNeighbor{
- {
- ID: "bfd-nbr-1",
- Address: "10.42.36.253",
- Interface: "eth0",
- },
- },
- RIPInstances: []RIPInstance{
- {
- ID: "rip-1",
- Name: "default",
- Version: 2,
- Enabled: true,
- RedistributeConnected: true,
- RedistributeStatic: true,
- },
- },
- RIPInterfaces: []RIPInterface{
- {
- ID: "rip-if-1",
- Instance: "default",
- Interface: "eth0",
- Metric: 1,
- Mode: "multicast",
- UseBFD: true,
- },
- },
- BabelInstances: []BabelInstance{
- {
- ID: "babel-1",
- Name: "default",
- RouterID: "10.42.62.254",
- Enabled: true,
- RedistributeConnected: true,
- },
- },
- BabelInterfaces: []BabelInterface{
- {
- ID: "babel-if-1",
- Instance: "default",
- Interface: "eth0",
- Type: "wired",
- RxCost: 10,
- HelloInterval: 4,
- UpdateInterval: 16,
- },
- },
- RAdvInstances: []RAdvInstance{
- {
- ID: "radv-1",
- Name: "default",
- Enabled: true,
- },
- },
- RAdvInterfaces: []RAdvInterface{
- {
- ID: "radv-if-1",
- Instance: "default",
- Interface: "eth0",
- MinRAInterval: 200,
- MaxRAInterval: 600,
- DefaultLifetime: 1800,
- Prefixes: []string{"2001:db8:1::/64"},
- RDNSS: []string{"2001:4860:4860::8888"},
- },
- },
- },
- }
- cfg := mgr.generateBirdConfig()
- // Verify all protocol blocks are generated
- if !strings.Contains(cfg, "protocol bfd bfd_main") {
- t.Errorf("Expected protocol bfd bfd_main in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, "protocol rip rip_default") {
- t.Errorf("Expected protocol rip in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, "protocol babel babel_default") {
- t.Errorf("Expected protocol babel in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, "protocol radv radv_default") {
- t.Errorf("Expected protocol radv in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, "bfd yes;") {
- t.Errorf("Expected bfd yes; in peer and interface, got:\n%s", cfg)
- }
- // Validate generated config directly with BIRD 2 compiler if bird binary is available
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_val_*.conf")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- if _, err := tmpFile.WriteString(cfg); err != nil {
- t.Fatalf("Failed to write temp file: %v", err)
- }
- tmpFile.Close()
- cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("bird -p validation failed: %s\nConfig:\n%s", string(out), cfg)
- }
- }
- }
- func TestBabelFallbackInterfaceAndInstanceManagement(t *testing.T) {
- tmpDir, err := os.MkdirTemp("", "debtik_babel_test_*")
- if err != nil {
- t.Fatalf("Failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
- mgr := &Manager{
- configPath: tmpDir + "/dynamic.json",
- config: DynamicConfig{
- BabelInstances: []BabelInstance{},
- BabelInterfaces: []BabelInterface{},
- },
- }
- // 1. Save Babel instance without explicit enabled flag
- inst, err := mgr.SaveBabelInstance(BabelInstance{
- Name: "internal-Maiden2",
- RouterID: "192.168.132.1",
- })
- if err != nil {
- t.Fatalf("SaveBabelInstance failed: %v", err)
- }
- if !inst.Enabled {
- t.Errorf("Expected inst.Enabled to be true when Disabled is false, got false")
- }
- // 2. Generate BIRD config when no interfaces exist - should generate fallback interface "*"
- cfg := mgr.generateBirdConfig()
- if !strings.Contains(cfg, `protocol babel babel_internal_Maiden2_0`) {
- t.Errorf("Expected protocol babel in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, `interface "*"`) {
- t.Errorf("Expected fallback interface \"*\" in config, got:\n%s", cfg)
- }
- // 3. Save Babel interface without explicit instance - should default to "internal-Maiden2"
- iface, err := mgr.SaveBabelInterface(BabelInterface{
- Interface: "eth0",
- RxCost: 96,
- })
- if err != nil {
- t.Fatalf("SaveBabelInterface failed: %v", err)
- }
- if iface.Instance != "internal-Maiden2" {
- t.Errorf("Expected iface.Instance to default to internal-Maiden2, got %q", iface.Instance)
- }
- // 4. Regenerate config - now should contain eth0 template
- cfg2 := mgr.generateBirdConfig()
- if !strings.Contains(cfg2, `interface "eth0"`) {
- t.Errorf("Expected interface \"eth0\" in config, got:\n%s", cfg2)
- }
- // 5. Validate with bird -p if bird is installed
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_babel_val_*.conf")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- if _, err := tmpFile.WriteString(cfg2); err != nil {
- t.Fatalf("Failed to write temp file: %v", err)
- }
- tmpFile.Close()
- cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("bird -p validation failed on babel config: %s\nConfig:\n%s", string(out), cfg2)
- }
- }
- }
- func TestBFDAndRIPAndRAdvManagement(t *testing.T) {
- tmpDir, err := os.MkdirTemp("", "debtik_protocols_test_*")
- if err != nil {
- t.Fatalf("Failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tmpDir)
- mgr := &Manager{
- configPath: tmpDir + "/dynamic.json",
- }
- // 1. Save BFD instance
- err = mgr.SaveBFDInstance(BFDInstance{Enabled: true})
- if err != nil {
- t.Fatalf("SaveBFDInstance failed: %v", err)
- }
- bfdInst := mgr.GetBFDInstance()
- if !bfdInst.Enabled {
- t.Errorf("Expected bfdInst.Enabled to be true")
- }
- // 2. Check BFD config generation with 0 interfaces - should contain fallback interface "*"
- cfg := mgr.generateBirdConfig()
- if !strings.Contains(cfg, `protocol bfd bfd_main`) {
- t.Errorf("Expected protocol bfd bfd_main in config, got:\n%s", cfg)
- }
- if !strings.Contains(cfg, `interface "*"`) {
- t.Errorf("Expected fallback interface \"*\" in BFD config, got:\n%s", cfg)
- }
- // 3. Save BFD multihop neighbor with local address
- _, err = mgr.SaveBFDNeighbor(BFDNeighbor{
- Address: "10.0.0.1",
- LocalAddress: "10.0.0.2",
- Multihop: true,
- })
- if err != nil {
- t.Fatalf("SaveBFDNeighbor failed: %v", err)
- }
- // 4. Save RIP instance without explicit enabled flag
- ripInst, err := mgr.SaveRIPInstance(RIPInstance{
- Name: "rip1",
- Version: 2,
- })
- if err != nil {
- t.Fatalf("SaveRIPInstance failed: %v", err)
- }
- if !ripInst.Enabled {
- t.Errorf("Expected ripInst.Enabled to be true, got false")
- }
- // 5. Save RAdv instance without explicit enabled flag
- radvInst, err := mgr.SaveRAdvInstance(RAdvInstance{
- Name: "radv1",
- })
- if err != nil {
- t.Fatalf("SaveRAdvInstance failed: %v", err)
- }
- if !radvInst.Enabled {
- t.Errorf("Expected radvInst.Enabled to be true, got false")
- }
- // 6. Verify config with BIRD compiler
- cfgAll := mgr.generateBirdConfig()
- if !strings.Contains(cfgAll, "neighbor 10.0.0.1 local 10.0.0.2 multihop;") {
- t.Errorf("Expected multihop neighbor with local address, got:\n%s", cfgAll)
- }
- if !strings.Contains(cfgAll, `protocol rip rip_rip1_0`) {
- t.Errorf("Expected protocol rip in config, got:\n%s", cfgAll)
- }
- if !strings.Contains(cfgAll, `protocol radv radv_radv1_0`) {
- t.Errorf("Expected protocol radv in config, got:\n%s", cfgAll)
- }
- // 7. Verify OSPF fallback interface when no templates exist
- mgr.config.OSPFInterfaces = nil
- cfgOspfFallback := mgr.generateBirdConfig()
- if !strings.Contains(cfgOspfFallback, `interface "*"`) {
- t.Errorf("Expected fallback wildcard interface for OSPF area, got:\n%s", cfgOspfFallback)
- }
- if _, err := exec.LookPath("bird"); err == nil {
- tmpFile, err := os.CreateTemp("", "bird_all_val_*.conf")
- if err != nil {
- t.Fatalf("Failed to create temp file: %v", err)
- }
- defer os.Remove(tmpFile.Name())
- if _, err := tmpFile.WriteString(cfgOspfFallback); err != nil {
- t.Fatalf("Failed to write temp file: %v", err)
- }
- tmpFile.Close()
- cmd := exec.Command("bird", "-p", "-c", tmpFile.Name())
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("bird -p validation failed on all protocols config: %s\nConfig:\n%s", string(out), cfgOspfFallback)
- }
- }
- }
- func TestRIPAndOSPFInstanceJSONUnmarshal(t *testing.T) {
- testCasesRIP := []struct {
- jsonStr string
- expectedVersion int
- }{
- {`{"name":"rip1","version":2}`, 2},
- {`{"name":"rip1","version":"2"}`, 2},
- {`{"name":"rip1","version":"rip2"}`, 2},
- {`{"name":"rip1","version":"ripv2"}`, 2},
- {`{"name":"ripng1","version":1}`, 1},
- {`{"name":"ripng1","version":"1"}`, 1},
- {`{"name":"ripng1","version":"ripng"}`, 1},
- {`{"name":"ripng1","version":"ng"}`, 1},
- {`{"name":"ripng1","version":"ipv6"}`, 1},
- {`{"name":"rip_default"}`, 2},
- }
- for idx, tc := range testCasesRIP {
- var inst RIPInstance
- if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
- t.Fatalf("Case %d failed to unmarshal: %v", idx, err)
- }
- if inst.Version != tc.expectedVersion {
- t.Errorf("Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
- }
- }
- testCasesOSPF := []struct {
- jsonStr string
- expectedVersion int
- }{
- {`{"name":"ospf1","version":2}`, 2},
- {`{"name":"ospf1","version":"2"}`, 2},
- {`{"name":"ospf1","version":"ospfv2"}`, 2},
- {`{"name":"ospf1","version":"v2"}`, 2},
- {`{"name":"ospf3","version":3}`, 3},
- {`{"name":"ospf3","version":"3"}`, 3},
- {`{"name":"ospf3","version":"ospfv3"}`, 3},
- {`{"name":"ospf3","version":"v3"}`, 3},
- {`{"name":"ospf3","version":"ipv6"}`, 3},
- {`{"name":"ospf_default"}`, 2},
- }
- for idx, tc := range testCasesOSPF {
- var inst OSPFInstance
- if err := json.Unmarshal([]byte(tc.jsonStr), &inst); err != nil {
- t.Fatalf("OSPF Case %d failed to unmarshal: %v", idx, err)
- }
- if inst.Version != tc.expectedVersion {
- t.Errorf("OSPF Case %d: expected version %d, got %d", idx, tc.expectedVersion, inst.Version)
- }
- }
- }
|