public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox-perl-rs v3 12/12] pve-rs: fabrics: add unit-tests for evpn l2vpn and l3vpn routes
Date: Fri,  7 Nov 2025 15:31:35 +0100	[thread overview]
Message-ID: <20251107143201.689035-22-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20251107143201.689035-1-s.hanreich@proxmox.com>

From: Gabriel Goller <g.goller@proxmox.com>

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 <g.goller@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 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<L3VPNRoute>);
 
 /// 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<L3VPNRoutes, an
 }
 
 /// One L2VPN route
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, PartialEq, Eq)]
 pub struct L2VPNRoute {
     mac: MacAddress,
     ip: IpAddr,
@@ -536,7 +536,7 @@ pub struct L2VPNRoute {
 }
 
 /// All L2VPN routes of a specific vnet
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, PartialEq, Eq)]
 pub struct L2VPNRoutes(Vec<L2VPNRoute>);
 
 /// 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


  parent reply	other threads:[~2025-11-07 14:38 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-07 14:31 [pve-devel] [PATCH common/manager/network/proxmox{-ve-rs, -perl-rs} v3 00/39] Improve status reporting for SDN / networking Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-common v3 1/2] iproute2: add helper for detecting bridge members Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-common v3 2/2] iproute2: add helper for querying vlan information Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 1/7] frr: make room for deserialization structs Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 2/7] frr: add deserialization types for openfabric and ospf Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 3/7] ve-config: add helper function to iterate over all nodes in all fabrics Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 4/7] ve-config: add optional tag property to vnet Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 5/7] frr: fix some route deserialization types Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 6/7] frr: add deserialization types for EVPN Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-ve-rs v3 7/7] add derive PartialEq, Eq and HashMap->BTreeMap for tests Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 01/12] pve-rs: firewall: cargo: fmt Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 02/12] pve-rs: firewall: add missing documentation comments Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 03/12] pve-rs: cargo: bump proxmox-apt and proxmox-ve-config versions Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 04/12] pve-rs: fabrics: update proxmox-frr import path Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 05/12] pve-rs: fabrics: fix clippy lint warnings Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 06/12] pve-rs: fabrics: add function to get status of fabric Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 07/12] pve-rs: fabrics: add function to get l2vpn and l3vpn routes for evpn Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 08/12] pve-rs: fabrics: add function to get routes learned by a fabric Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 09/12] pve-rs: fabrics: add function to get the interfaces used for " Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 10/12] pve-rs: fabrics: add function to get the neighbors " Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH proxmox-perl-rs v3 11/12] pve-rs: fabrics: add unit-tests for fabrics Stefan Hanreich
2025-11-07 14:31 ` Stefan Hanreich [this message]
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 1/9] refactor: rework api module structure for the /nodes/{node}/sdn subdir Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 2/9] fabrics: add fabrics status to SDN::status function Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 3/9] sdn: status: add zone type to sdn resource Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 4/9] api: nodes: fabrics: add endpoint for querying route status Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 5/9] api: nodes: fabrics: add endpoint for querying neighbor information Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 6/9] api: nodes: fabrics: add endpoint for querying interface status Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 7/9] api: nodes: zones: add bridge status Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 8/9] api: nodes: zones: add ip vrf endpoint for evpn zones Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-network v3 9/9] api: nodes: vnets: add mac-vrf endpoint for evpn vnets Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 1/9] api: nodes: use new status module for sdn subdirectory Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 2/9] refactor: ui: sdn browser: parametrize zone content panel Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 3/9] pvestatd: add network resource to status reporting Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 4/9] pvestatd: sdn: adapt to changes in " Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 5/9] ui: resource tree: add network resource Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 6/9] ui: network browser: Add ip-vrf panel for evpn zones Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 7/9] ui: network browser: add mac vrf panel Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 8/9] ui: network browser: add zone bridge view Stefan Hanreich
2025-11-07 14:31 ` [pve-devel] [PATCH pve-manager v3 9/9] ui: sdn: status view: adapt to new network resource Stefan Hanreich

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251107143201.689035-22-s.hanreich@proxmox.com \
    --to=s.hanreich@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal