diff --git a/pkg/agent/dataplane_test.go b/pkg/agent/dataplane_test.go new file mode 100644 index 00000000..b3930db0 --- /dev/null +++ b/pkg/agent/dataplane_test.go @@ -0,0 +1,246 @@ +// Copyright 2025 Hedgehog +// SPDX-License-Identifier: Apache-2.0 + +package agent + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.githedgehog.com/gateway-proto/pkg/dataplane" + "go.githedgehog.com/gateway/api/gateway/v1alpha1" + gwintapi "go.githedgehog.com/gateway/api/gwint/v1alpha1" +) + +func agb(name string, f ...func(ag *gwintapi.GatewayAgent)) *gwintapi.GatewayAgent { + agentBase := &gwintapi.GatewayAgent{ + Spec: gwintapi.GatewayAgentSpec{ + Gateway: v1alpha1.GatewaySpec{ + ProtocolIP: "192.0.2.1/32", + VTEPIP: "192.0.2.2", + VTEPMAC: "aa:bb:cc:dd:ee:ff", + VTEPMTU: 1500, + ASN: 65001, + Interfaces: map[string]v1alpha1.GatewayInterface{ + "eth0": {IPs: []string{"10.0.0.1"}, MTU: 1500}, + }, + Neighbors: []v1alpha1.GatewayBGPNeighbor{ + {IP: "192.0.2.3", ASN: 65002, Source: "eth0"}, + }, + }, + VPCs: map[string]gwintapi.VPCInfoData{ + "vpc-01": { + VPCInfoSpec: v1alpha1.VPCInfoSpec{ + VNI: 100, + Subnets: map[string]*v1alpha1.VPCInfoSubnet{ + "subnet1": {CIDR: "10.0.1.0/24"}, + }, + }, + }, + "vpc-02": { + VPCInfoSpec: v1alpha1.VPCInfoSpec{ + VNI: 200, + Subnets: map[string]*v1alpha1.VPCInfoSubnet{ + "subnet1": {CIDR: "10.0.2.0/24"}, + }, + }, + }, + }, + Peerings: map[string]v1alpha1.PeeringSpec{}, + }, + } + agentBase.Name = name + agentBase.Namespace = "fab" + + for _, fn := range f { + fn(agentBase) + } + + return agentBase +} + +func dpb(name string, f ...func(dp *dataplane.GatewayConfig)) *dataplane.GatewayConfig { + dpCfgBase := &dataplane.GatewayConfig{ + Generation: 0, + Device: &dataplane.Device{ + Driver: dataplane.PacketDriver_KERNEL, + Hostname: name, + Loglevel: dataplane.LogLevel_DEBUG, + }, + Underlay: &dataplane.Underlay{ + Vrfs: []*dataplane.VRF{ + { + Name: "default", + Interfaces: []*dataplane.Interface{ + { + Name: "lo", + Ipaddrs: []string{"192.0.2.2"}, + Type: dataplane.IfType_IF_TYPE_LOOPBACK, + Role: dataplane.IfRole_IF_ROLE_FABRIC, + }, + { + Name: "vtep", + Ipaddrs: []string{"192.0.2.2"}, + Type: dataplane.IfType_IF_TYPE_VTEP, + Role: dataplane.IfRole_IF_ROLE_FABRIC, + Macaddr: ptr("aa:bb:cc:dd:ee:ff"), + Mtu: ptr(uint32(1500)), + }, + { + Name: "eth0", + Ipaddrs: []string{"10.0.0.1"}, + Type: dataplane.IfType_IF_TYPE_ETHERNET, + Role: dataplane.IfRole_IF_ROLE_FABRIC, + Mtu: ptr(uint32(1500)), + }, + }, + Router: &dataplane.RouterConfig{ + Asn: "65001", + RouterId: "192.0.2.1", + Neighbors: []*dataplane.BgpNeighbor{ + { + Address: "192.0.2.3", + RemoteAsn: "65002", + AfActivate: []dataplane.BgpAF{ + dataplane.BgpAF_IPV4_UNICAST, + dataplane.BgpAF_L2VPN_EVPN, + }, + UpdateSource: &dataplane.BgpNeighborUpdateSource{ + Source: &dataplane.BgpNeighborUpdateSource_Interface{ + Interface: "eth0", + }, + }, + }, + }, + Ipv4Unicast: &dataplane.BgpAddressFamilyIPv4{ + Networks: []string{"192.0.2.2"}, + RedistributeConnected: false, + RedistributeStatic: false, + }, + L2VpnEvpn: &dataplane.BgpAddressFamilyL2VpnEvpn{ + AdvertiseAllVni: true, + }, + }, + }, + }, + }, + Overlay: &dataplane.Overlay{ + Vpcs: []*dataplane.VPC{ + { + Name: "vpc-01", + Id: "", + Vni: 100, + }, + { + Name: "vpc-02", + Id: "", + Vni: 200, + }, + }, + }, + } + + for _, fn := range f { + fn(dpCfgBase) + } + + return dpCfgBase +} + +func TestBuildDataplaneConfig(t *testing.T) { + tests := []struct { + name string + agent *gwintapi.GatewayAgent + outputCfg *dataplane.GatewayConfig + wantErr bool + }{ + { + name: "single expose with CIDR", + agent: agb("agent1", func(ag *gwintapi.GatewayAgent) { + ag.Spec.Peerings = map[string]v1alpha1.PeeringSpec{ + "peering1": { + Peering: map[string]*v1alpha1.PeeringEntry{ + "vpc-01": { + Expose: []v1alpha1.PeeringEntryExpose{ + { + IPs: []v1alpha1.PeeringEntryIP{ + {CIDR: "10.0.1.0/24"}, + }, + As: []v1alpha1.PeeringEntryAs{ + {CIDR: "172.96.1.0/24"}, + }, + }, + }, + }, + "vpc-02": { + Expose: []v1alpha1.PeeringEntryExpose{ + { + IPs: []v1alpha1.PeeringEntryIP{ + {VPCSubnet: "subnet1"}, + }, + As: []v1alpha1.PeeringEntryAs{ + {CIDR: "172.96.2.0/24"}, + }, + }, + }, + }, + }, + }, + } + }), + outputCfg: dpb("agent1", func(dp *dataplane.GatewayConfig) { + dp.Overlay.Peerings = []*dataplane.VpcPeering{ + { + Name: "peering1", + For: []*dataplane.PeeringEntryFor{ + { + Vpc: "vpc-01", + Expose: []*dataplane.Expose{ + { + Ips: []*dataplane.PeeringIPs{ + {Rule: &dataplane.PeeringIPs_Cidr{Cidr: "10.0.1.0/24"}}, + }, + As: []*dataplane.PeeringAs{ + {Rule: &dataplane.PeeringAs_Cidr{Cidr: "172.96.1.0/24"}}, + }, + }, + }, + }, + { + Vpc: "vpc-02", + Expose: []*dataplane.Expose{ + { + Ips: []*dataplane.PeeringIPs{ + {Rule: &dataplane.PeeringIPs_Cidr{Cidr: "10.0.2.0/24"}}, + }, + As: []*dataplane.PeeringAs{ + {Rule: &dataplane.PeeringAs_Cidr{Cidr: "172.96.2.0/24"}}, + }, + }, + }, + }, + }, + }, + } + }), + wantErr: false, + }, + // Add more cases for multiple exposes, Not, empty exposes, etc. + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildDataplaneConfig(tt.agent) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, tt.outputCfg, got) + }) + } +} + +// Helper for pointer values +func ptr[T any](v T) *T { return &v }