From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id AEDC71FF16B for ; Fri, 7 Nov 2025 15:38:18 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B95E314AFB; Fri, 7 Nov 2025 15:38:44 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Fri, 7 Nov 2025 15:31:35 +0100 Message-ID: <20251107143201.689035-22-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251107143201.689035-1-s.hanreich@proxmox.com> References: <20251107143201.689035-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.176 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH proxmox-perl-rs v3 12/12] pve-rs: fabrics: add unit-tests for evpn l2vpn and l3vpn routes X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" From: Gabriel Goller Add unit-tests for evpn l2vpn and l3vpn routes that provide a frr output and check the parsed struct that is then returned from the pve-network api. In order to test the returned structs PartialEq and Eq have been derived on a few structs and the hostname retrieval has been pulled out to mock the functions. Signed-off-by: Gabriel Goller Signed-off-by: Stefan Hanreich --- pve-rs/src/sdn/status.rs | 348 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 343 insertions(+), 5 deletions(-) diff --git a/pve-rs/src/sdn/status.rs b/pve-rs/src/sdn/status.rs index 27a3c08..6e75562 100644 --- a/pve-rs/src/sdn/status.rs +++ b/pve-rs/src/sdn/status.rs @@ -474,7 +474,7 @@ pub fn get_status( Ok(stats) } /// Common for nexthops, they can be either a interface name or a ip addr -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] #[serde(untagged)] pub enum IpAddrOrInterfaceName { /// IpAddr @@ -484,7 +484,7 @@ pub enum IpAddrOrInterfaceName { } /// One L3VPN route -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct L3VPNRoute { ip: Cidr, protocol: String, @@ -493,7 +493,7 @@ pub struct L3VPNRoute { } /// All L3VPN routes of a zone -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct L3VPNRoutes(Vec); /// Convert parsed routes from frr into l3vpn routes, this means we need to match against the vrf @@ -528,7 +528,7 @@ pub fn get_l3vpn_routes(vrf: &str, routes: de::Routes) -> Result); /// Convert the parsed frr evpn struct into an array of structured L2VPN routes @@ -2314,4 +2314,342 @@ mod tests { assert_eq!(reference_fabric2, output_fabric2); } } + + mod evpn { + use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, + }; + + use super::super::*; + + #[test] + fn routes_l3vpn() { + let json_output = r#" + { + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "prefixLen": 0, + "protocol": "kernel", + "vrfId": 14, + "vrfName": "vrf_test", + "selected": true, + "destSelected": true, + "distance": 255, + "metric": 8192, + "installed": true, + "table": 1001, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthopGroupId": 82, + "installedNexthopGroupId": 82, + "uptime": "00:03:44", + "nexthops": [ + { + "flags": 3, + "fib": true, + "unreachable": true, + "reject": true, + "active": true, + "weight": 1 + } + ] + } + ], + "172.16.100.0/24": [ + { + "prefix": "172.16.100.0/24", + "prefixLen": 24, + "protocol": "connected", + "vrfId": 14, + "vrfName": "vrf_test", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 1001, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthopGroupId": 80, + "installedNexthopGroupId": 80, + "uptime": "00:03:44", + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceIndex": 13, + "interfaceName": "test", + "active": true, + "weight": 1 + } + ] + }, + { + "prefix": "172.16.100.0/24", + "prefixLen": 24, + "protocol": "kernel", + "vrfId": 14, + "vrfName": "vrf_test", + "distance": 0, + "metric": 0, + "installed": true, + "table": 1001, + "internalStatus": 16, + "internalFlags": 0, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthopGroupId": 78, + "uptime": "00:03:44", + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceIndex": 13, + "interfaceName": "test", + "vrf": "default", + "active": true, + "weight": 1 + } + ] + } + ], + "172.16.100.1/32": [ + { + "prefix": "172.16.100.1/32", + "prefixLen": 32, + "protocol": "local", + "vrfId": 14, + "vrfName": "vrf_test", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 1001, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthopGroupId": 80, + "installedNexthopGroupId": 80, + "uptime": "00:03:44", + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceIndex": 13, + "interfaceName": "test", + "active": true, + "weight": 1 + } + ] + } + ], + "172.16.100.2/32": [ + { + "prefix": "172.16.100.2/32", + "prefixLen": 32, + "protocol": "bgp", + "vrfId": 14, + "vrfName": "vrf_test", + "selected": true, + "destSelected": true, + "distance": 200, + "metric": 0, + "installed": true, + "table": 1001, + "internalStatus": 16, + "internalFlags": 13, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthopGroupId": 88, + "installedNexthopGroupId": 88, + "uptime": "00:01:22", + "nexthops": [ + { + "flags": 267, + "fib": true, + "ip": "172.16.6.1", + "afi": "ipv4", + "interfaceIndex": 16, + "interfaceName": "vrfbr_test", + "active": true, + "onLink": true, + "weight": 1 + } + ] + } + ] + } + + "#; + + let routes: de::Routes = if json_output.is_empty() { + de::Routes::default() + } else { + serde_json::from_str(json_output).expect("error parsing json output") + }; + + let zone = "test"; + + let output = get_l3vpn_routes(&format!("vrf_{zone}"), routes) + .expect("error converting vtysh output"); + + let reference = L3VPNRoutes(vec![ + L3VPNRoute { + ip: Cidr::from_str("0.0.0.0/0").expect("valid cidr"), + protocol: "kernel".to_owned(), + metric: 8192, + nexthops: vec![], + }, + L3VPNRoute { + ip: Cidr::from_str("172.16.100.0/24").expect("valid cidr"), + protocol: "connected".to_owned(), + metric: 0, + nexthops: vec![IpAddrOrInterfaceName::InterfaceName("test".to_owned())], + }, + L3VPNRoute { + ip: Cidr::from_str("172.16.100.0/24").expect("valid cidr"), + protocol: "kernel".to_owned(), + metric: 0, + nexthops: vec![IpAddrOrInterfaceName::InterfaceName("test".to_owned())], + }, + L3VPNRoute { + ip: Cidr::from_str("172.16.100.1/32").expect("valid cidr"), + protocol: "local".to_owned(), + metric: 0, + nexthops: vec![IpAddrOrInterfaceName::InterfaceName("test".to_owned())], + }, + L3VPNRoute { + ip: Cidr::from_str("172.16.100.2/32").expect("valid cidr"), + protocol: "bgp".to_owned(), + metric: 0, + nexthops: vec![IpAddrOrInterfaceName::IpAddr(IpAddr::V4( + Ipv4Addr::from_str("172.16.6.1").expect("valid ip addr"), + ))], + }, + ]); + assert_eq!(reference, output); + } + + #[test] + fn routes_l2vpn() { + let json_output = r#" + { + "[2]:[0]:[48]:[00:00:00:00:00:00]:[32]:[172.16.100.2]":{ + "prefix":"[2]:[0]:[48]:[00:00:00:00:00:00]:[32]:[172.16.100.2]", + "prefixLen":352, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"bc:24:11:02:45:ae", + "ipLen":32, + "ip":"172.16.100.2", + "locPrf":100, + "weight":0, + "peerId":"172.16.6.1", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:65000:100 RT:65000:101 ET:8 Rmac:e2:44:0e:6f:78:72" + }, + "nexthops":[ + { + "ip":"172.16.6.1", + "hostname":"node1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[00:00:00:00:00:00]:[128]:[fe80::be24:11ff:fe02:45ae]":{ + "prefix":"[2]:[0]:[48]:[00:00:00:00:00:00]:[128]:[fe80::be24:11ff:fe02:45ae]", + "prefixLen":352, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"bc:24:11:02:45:ae", + "ipLen":128, + "ip":"fe80::be24:11ff:fe02:45ae", + "locPrf":100, + "weight":0, + "peerId":"172.16.6.1", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:65000:100 ET:8" + }, + "nexthops":[ + { + "ip":"172.16.6.1", + "hostname":"node1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "numPrefix":2, + "numPaths":2 + } + + "#; + + let routes: de::evpn::Routes = if json_output.is_empty() { + de::evpn::Routes::default() + } else { + serde_json::from_str(json_output).expect("error parsing json output") + }; + + let output = get_l2vpn_routes(routes).expect("error converting vtysh output"); + + let reference = L2VPNRoutes(vec![ + L2VPNRoute { + mac: MacAddress::from_str("bc:24:11:02:45:ae").expect("valid mac address"), + ip: IpAddr::V6( + Ipv6Addr::from_str("fe80::be24:11ff:fe02:45ae").expect("valid ip address"), + ), + nexthop: IpAddr::V4( + Ipv4Addr::from_str("172.16.6.1").expect("valid ip address"), + ), + }, + L2VPNRoute { + mac: MacAddress::from_str("bc:24:11:02:45:ae").expect("valid mac address"), + ip: IpAddr::V4(Ipv4Addr::from_str("172.16.100.2").expect("valid ip address")), + nexthop: IpAddr::V4( + Ipv4Addr::from_str("172.16.6.1").expect("valid ip address"), + ), + }, + ]); + assert_eq!(reference, output); + } + } } -- 2.47.3 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel