[pve-devel] [PATCH proxmox-ve-rs 2/3] ve-config: add IS-IS fabric config parsing and frr config generation
Gabriel Goller
g.goller at proxmox.com
Tue Aug 19 15:19:01 CEST 2025
Add the necessary types to parse IS-IS fabrics from the fabrics.cfg
config file and convert that config into the frr config types.
Everything is quite similar to OpenFabric, but it's worth to keep them
separate because they will diverge in the future.
Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
proxmox-ve-config/src/sdn/fabric/frr.rs | 210 ++++++++++++++++++
proxmox-ve-config/src/sdn/fabric/mod.rs | 130 +++++++++++
.../src/sdn/fabric/section_config/fabric.rs | 22 ++
.../src/sdn/fabric/section_config/mod.rs | 19 ++
.../src/sdn/fabric/section_config/node.rs | 21 ++
.../fabric/section_config/protocol/isis.rs | 151 +++++++++++++
.../sdn/fabric/section_config/protocol/mod.rs | 1 +
7 files changed, 554 insertions(+)
create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs
index 486f7dc51dcb..3f4ab06598f2 100644
--- a/proxmox-ve-config/src/sdn/fabric/frr.rs
+++ b/proxmox-ve-config/src/sdn/fabric/frr.rs
@@ -13,6 +13,7 @@ use proxmox_sdn_types::net::Net;
use crate::common::valid::Valid;
use crate::sdn::fabric::section_config::protocol::{
+ isis::{IsisInterfaceProperties, IsisProperties},
openfabric::{OpenfabricInterfaceProperties, OpenfabricProperties},
ospf::OspfInterfaceProperties,
};
@@ -154,6 +155,126 @@ pub fn build_fabric(
frr_config.protocol_routemaps.insert(protocol_routemap);
}
}
+ FabricEntry::Isis(isis_entry) => {
+ // Get the current node of this fabric, if it doesn't exist, skip this fabric and
+ // don't generate any FRR config.
+ let Ok(node) = isis_entry.node_section(¤t_node) else {
+ continue;
+ };
+
+ if current_net.is_none() {
+ current_net = match (node.ip(), node.ip6()) {
+ (Some(ip), _) => Some(ip.into()),
+ (_, Some(ip6)) => Some(ip6.into()),
+ (_, _) => None,
+ }
+ }
+
+ let net = current_net
+ .as_ref()
+ .ok_or_else(|| anyhow::anyhow!("no IPv4 or IPv6 set for node"))?;
+ let (router_name, router_item) = build_isis_router(fabric_id, net.clone())?;
+ frr_config.router.insert(router_name, router_item);
+
+ // Create dummy interface for fabric
+ let (interface, interface_name) = build_isis_dummy_interface(
+ fabric_id,
+ node.ip().is_some(),
+ node.ip6().is_some(),
+ )?;
+
+ if frr_config
+ .interfaces
+ .insert(interface_name, interface)
+ .is_some()
+ {
+ tracing::error!(
+ "An interface with the same name as the dummy interface exists"
+ );
+ }
+
+ let fabric = isis_entry.fabric_section();
+
+ for interface in node.properties().interfaces.iter() {
+ let (interface, interface_name) = build_isis_interface(
+ fabric_id,
+ interface,
+ fabric.properties(),
+ node.ip().is_some(),
+ node.ip6().is_some(),
+ )?;
+
+ if frr_config
+ .interfaces
+ .insert(interface_name, interface)
+ .is_some()
+ {
+ tracing::warn!("An interface cannot be in multiple IS-IS fabrics");
+ }
+ }
+
+ if let Some(ipv4cidr) = fabric.ip_prefix() {
+ let rule = AccessListRule {
+ action: AccessAction::Permit,
+ network: Cidr::from(ipv4cidr),
+ seq: None,
+ };
+ let access_list_name =
+ AccessListName::new(format!("pve_isis_{}_ips", fabric_id));
+ frr_config.access_lists.push(AccessList {
+ name: access_list_name,
+ rules: vec![rule],
+ });
+ }
+ if let Some(ipv6cidr) = fabric.ip6_prefix() {
+ let rule = AccessListRule {
+ action: AccessAction::Permit,
+ network: Cidr::from(ipv6cidr),
+ seq: None,
+ };
+ let access_list_name =
+ AccessListName::new(format!("pve_isis_{}_ip6s", fabric_id));
+ frr_config.access_lists.push(AccessList {
+ name: access_list_name,
+ rules: vec![rule],
+ });
+ }
+
+ if let Some(ipv4) = node.ip() {
+ // create route-map
+ frr_config.routemaps.push(build_isis_routemap(
+ fabric_id,
+ IpAddr::V4(ipv4),
+ routemap_seq,
+ ));
+ routemap_seq += 10;
+
+ let protocol_routemap = ProtocolRouteMap {
+ is_ipv6: false,
+ protocol: ProtocolType::Isis,
+ routemap_name: RouteMapName::new("pve_isis".to_owned()),
+ };
+
+ frr_config.protocol_routemaps.insert(protocol_routemap);
+ }
+ if let Some(ipv6) = node.ip6() {
+ // create route-map
+ frr_config.routemaps.push(build_isis_routemap(
+ fabric_id,
+ IpAddr::V6(ipv6),
+ routemap_seq,
+ ));
+ routemap_seq += 10;
+
+ let protocol_routemap = ProtocolRouteMap {
+ is_ipv6: true,
+ protocol: ProtocolType::Isis,
+ routemap_name: RouteMapName::new("pve_isis6".to_owned()),
+ };
+
+ frr_config.protocol_routemaps.insert(protocol_routemap);
+ }
+ }
FabricEntry::Ospf(ospf_entry) => {
let Ok(node) = ospf_entry.node_section(¤t_node) else {
continue;
@@ -253,6 +374,18 @@ fn build_openfabric_router(
Ok((router_name, router_item))
}
+/// Helper that builds a IS-IS router from a fabric_id and a [`Net`].
+fn build_isis_router(
+ fabric_id: &FabricId,
+ net: Net,
+) -> Result<(RouterName, Router), anyhow::Error> {
+ let isis_frr_router = proxmox_frr::isis::IsisRouter { net };
+ let router_item = Router::Isis(isis_frr_router);
+ let frr_word_id = FrrWord::new(fabric_id.to_string())?;
+ let router_name = RouterName::Isis(frr_word_id.into());
+ Ok((router_name, router_item))
+}
+
/// Helper that builds a OSPF interface from an [`ospf::Area`] and the [`OspfInterfaceProperties`].
fn build_ospf_interface(
area: ospf::Area,
@@ -361,6 +494,83 @@ fn build_openfabric_routemap(fabric_id: &FabricId, router_ip: IpAddr, seq: u32)
}
}
+/// Helper that builds the IS-IS interface.
+///
+/// Takes the [`FabricId`], [`IsisInterfaceProperties`], [`IsisProperties`] and flags for
+/// ipv4 and ipv6.
+fn build_isis_interface(
+ fabric_id: &FabricId,
+ interface: &IsisInterfaceProperties,
+ fabric_config: &IsisProperties,
+ is_ipv4: bool,
+ is_ipv6: bool,
+) -> Result<(Interface, InterfaceName), anyhow::Error> {
+ let frr_word = FrrWord::new(fabric_id.to_string())?;
+ let mut frr_interface = proxmox_frr::isis::IsisInterface {
+ fabric_id: frr_word.into(),
+ // Every interface is not passive by default
+ passive: None,
+ // Get properties from fabric
+ hello_interval: fabric_config.hello_interval,
+ csnp_interval: fabric_config.csnp_interval,
+ hello_multiplier: interface.hello_multiplier,
+ is_ipv4,
+ is_ipv6,
+ point_to_point: !interface.ip.is_some(),
+ };
+ // If no specific hello_interval is set, get default one from fabric
+ // config
+ if frr_interface.hello_interval.is_none() {
+ frr_interface.hello_interval = fabric_config.hello_interval;
+ }
+ let interface_name = InterfaceName::Isis(interface.name.as_str().try_into()?);
+ Ok((frr_interface.into(), interface_name))
+}
+
+/// Helper that builds a IS-IS interface using a [`FabricId`] and ipv4/6 flags.
+fn build_isis_dummy_interface(
+ fabric_id: &FabricId,
+ is_ipv4: bool,
+ is_ipv6: bool,
+) -> Result<(Interface, InterfaceName), anyhow::Error> {
+ let frr_word = FrrWord::new(fabric_id.to_string())?;
+ let frr_interface = proxmox_frr::isis::IsisInterface {
+ fabric_id: frr_word.into(),
+ hello_interval: None,
+ passive: Some(true),
+ csnp_interval: None,
+ hello_multiplier: None,
+ is_ipv4,
+ is_ipv6,
+ // Note: doesn't matter the interface is passive anyway
+ point_to_point: false,
+ };
+ let interface_name = InterfaceName::Isis(format!("dummy_{}", fabric_id).try_into()?);
+ Ok((frr_interface.into(), interface_name))
+}
+
+/// Helper that builds a RouteMap for the IS-IS protocol.
+fn build_isis_routemap(fabric_id: &FabricId, router_ip: IpAddr, seq: u32) -> RouteMap {
+ let routemap_name = match router_ip {
+ IpAddr::V4(_) => RouteMapName::new("pve_isis".to_owned()),
+ IpAddr::V6(_) => RouteMapName::new("pve_isis6".to_owned()),
+ };
+ RouteMap {
+ name: routemap_name.clone(),
+ seq,
+ action: AccessAction::Permit,
+ matches: vec![match router_ip {
+ IpAddr::V4(_) => RouteMapMatch::V4(RouteMapMatchInner::IpAddress(AccessListName::new(
+ format!("pve_isis_{fabric_id}_ips"),
+ ))),
+ IpAddr::V6(_) => RouteMapMatch::V6(RouteMapMatchInner::IpAddress(AccessListName::new(
+ format!("pve_isis_{fabric_id}_ip6s"),
+ ))),
+ }],
+ sets: vec![RouteMapSet::IpSrc(router_ip)],
+ }
+}
+
/// Helper that builds a RouteMap for the OSPF protocol.
fn build_ospf_dummy_routemap(
fabric_id: &FabricId,
diff --git a/proxmox-ve-config/src/sdn/fabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/mod.rs
index 58a06f9423cb..d58d904b038c 100644
--- a/proxmox-ve-config/src/sdn/fabric/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/mod.rs
@@ -20,6 +20,10 @@ use crate::sdn::fabric::section_config::node::{
api::{NodeDataUpdater, NodeDeletableProperties, NodeUpdater},
Node, NodeId, NodeSection,
};
+use crate::sdn::fabric::section_config::protocol::isis::{
+ IsisDeletableProperties, IsisNodeDeletableProperties, IsisNodeProperties,
+ IsisNodePropertiesUpdater, IsisProperties, IsisPropertiesUpdater,
+};
use crate::sdn::fabric::section_config::protocol::openfabric::{
OpenfabricDeletableProperties, OpenfabricNodeDeletableProperties, OpenfabricNodeProperties,
OpenfabricNodePropertiesUpdater, OpenfabricProperties, OpenfabricPropertiesUpdater,
@@ -192,6 +196,36 @@ impl Entry<OpenfabricProperties, OpenfabricNodeProperties> {
}
}
+impl Entry<IsisProperties, IsisNodeProperties> {
+ /// Get the IS-IS fabric config.
+ ///
+ /// This method is implemented for [`Entry<IsisProperties, IsisNodeProperties>`],
+ /// so it is guaranteed that a [`FabricSection<IsisProperties>`] is returned.
+ pub fn fabric_section(&self) -> &FabricSection<IsisProperties> {
+ if let Fabric::Isis(section) = &self.fabric {
+ return section;
+ }
+
+ unreachable!();
+ }
+
+ /// Get the IS-IS node config for the given node_id.
+ ///
+ /// This method is implemented for [`Entry<IsisProperties, IsisNodeProperties>`],
+ /// so it is guaranteed that a [`NodeSection<IsisNodeProperties>`] is returned.
+ /// An error is returned if the node is not found.
+ pub fn node_section(
+ &self,
+ id: &NodeId,
+ ) -> Result<&NodeSection<IsisNodeProperties>, FabricConfigError> {
+ if let Node::Isis(section) = self.get_node(id)? {
+ return Ok(section);
+ }
+
+ unreachable!();
+ }
+}
+
impl Entry<OspfProperties, OspfNodeProperties> {
/// Get the OSPF fabric config.
///
@@ -229,6 +263,7 @@ impl Entry<OspfProperties, OspfNodeProperties> {
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
pub enum FabricEntry {
Openfabric(Entry<OpenfabricProperties, OpenfabricNodeProperties>),
+ Isis(Entry<IsisProperties, IsisNodeProperties>),
Ospf(Entry<OspfProperties, OspfNodeProperties>),
}
@@ -240,6 +275,7 @@ impl FabricEntry {
(FabricEntry::Openfabric(entry), Node::Openfabric(node_section)) => {
entry.add_node(node_section)
}
+ (FabricEntry::Isis(entry), Node::Isis(node_section)) => entry.add_node(node_section),
(FabricEntry::Ospf(entry), Node::Ospf(node_section)) => entry.add_node(node_section),
_ => Err(FabricConfigError::ProtocolMismatch),
}
@@ -250,6 +286,7 @@ impl FabricEntry {
pub fn get_node(&self, id: &NodeId) -> Result<&Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.get_node(id),
+ FabricEntry::Isis(entry) => entry.get_node(id),
FabricEntry::Ospf(entry) => entry.get_node(id),
}
}
@@ -259,6 +296,7 @@ impl FabricEntry {
pub fn get_node_mut(&mut self, id: &NodeId) -> Result<&mut Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.get_node_mut(id),
+ FabricEntry::Isis(entry) => entry.get_node_mut(id),
FabricEntry::Ospf(entry) => entry.get_node_mut(id),
}
}
@@ -307,6 +345,38 @@ impl FabricEntry {
Ok(())
}
+ (Node::Isis(node_section), NodeUpdater::Isis(updater)) => {
+ let NodeDataUpdater::<IsisNodePropertiesUpdater, IsisNodeDeletableProperties> {
+ ip,
+ ip6,
+ properties: IsisNodePropertiesUpdater { interfaces },
+ delete,
+ } = updater;
+
+ if let Some(ip) = ip {
+ node_section.ip = Some(ip);
+ }
+
+ if let Some(ip) = ip6 {
+ node_section.ip6 = Some(ip);
+ }
+
+ if let Some(interfaces) = interfaces {
+ node_section.properties.interfaces = interfaces;
+ }
+
+ for property in delete {
+ match property {
+ NodeDeletableProperties::Ip => node_section.ip = None,
+ NodeDeletableProperties::Ip6 => node_section.ip6 = None,
+ NodeDeletableProperties::Protocol(
+ IsisNodeDeletableProperties::Interfaces,
+ ) => node_section.properties.interfaces = Vec::new(),
+ }
+ }
+
+ Ok(())
+ }
(Node::Ospf(node_section), NodeUpdater::Ospf(updater)) => {
let NodeDataUpdater::<OspfNodePropertiesUpdater, OspfNodeDeletableProperties> {
ip,
@@ -347,6 +417,7 @@ impl FabricEntry {
pub fn nodes(&self) -> impl Iterator<Item = (&NodeId, &Node)> + '_ {
match self {
FabricEntry::Openfabric(entry) => entry.nodes.iter(),
+ FabricEntry::Isis(entry) => entry.nodes.iter(),
FabricEntry::Ospf(entry) => entry.nodes.iter(),
}
}
@@ -355,6 +426,7 @@ impl FabricEntry {
pub fn delete_node(&mut self, id: &NodeId) -> Result<Node, FabricConfigError> {
match self {
FabricEntry::Openfabric(entry) => entry.delete_node(id),
+ FabricEntry::Isis(entry) => entry.delete_node(id),
FabricEntry::Ospf(entry) => entry.delete_node(id),
}
}
@@ -364,6 +436,7 @@ impl FabricEntry {
pub fn into_section_config(self) -> (Fabric, Vec<Node>) {
match self {
FabricEntry::Openfabric(entry) => entry.into_pair(),
+ FabricEntry::Isis(entry) => entry.into_pair(),
FabricEntry::Ospf(entry) => entry.into_pair(),
}
}
@@ -372,6 +445,7 @@ impl FabricEntry {
pub fn fabric(&self) -> &Fabric {
match self {
FabricEntry::Openfabric(entry) => &entry.fabric,
+ FabricEntry::Isis(entry) => &entry.fabric,
FabricEntry::Ospf(entry) => &entry.fabric,
}
}
@@ -380,6 +454,7 @@ impl FabricEntry {
pub fn fabric_mut(&mut self) -> &mut Fabric {
match self {
FabricEntry::Openfabric(entry) => &mut entry.fabric,
+ FabricEntry::Isis(entry) => &mut entry.fabric,
FabricEntry::Ospf(entry) => &mut entry.fabric,
}
}
@@ -391,6 +466,7 @@ impl From<Fabric> for FabricEntry {
Fabric::Openfabric(fabric_section) => {
FabricEntry::Openfabric(Entry::new(fabric_section))
}
+ Fabric::Isis(fabric_section) => FabricEntry::Isis(Entry::new(fabric_section)),
Fabric::Ospf(fabric_section) => FabricEntry::Ospf(Entry::new(fabric_section)),
}
}
@@ -573,6 +649,13 @@ impl Validatable for FabricConfig {
return Err(FabricConfigError::DuplicateInterface);
}
}
+ Node::Isis(node_section) => {
+ if !node_section.properties().interfaces().all(|interface| {
+ node_interfaces.insert((node_id, interface.name.as_str()))
+ }) {
+ return Err(FabricConfigError::DuplicateInterface);
+ }
+ }
}
}
@@ -689,6 +772,53 @@ impl FabricConfig {
Ok(())
}
+ (Fabric::Isis(fabric_section), FabricUpdater::Isis(updater)) => {
+ let FabricSectionUpdater::<IsisPropertiesUpdater, IsisDeletableProperties> {
+ ip_prefix,
+ ip6_prefix,
+ properties:
+ IsisPropertiesUpdater {
+ hello_interval,
+ csnp_interval,
+ },
+ delete,
+ } = updater;
+
+ if let Some(prefix) = ip_prefix {
+ fabric_section.ip_prefix = Some(prefix);
+ }
+
+ if let Some(prefix) = ip6_prefix {
+ fabric_section.ip6_prefix = Some(prefix);
+ }
+
+ if let Some(hello_interval) = hello_interval {
+ fabric_section.properties.hello_interval = Some(hello_interval);
+ }
+
+ if let Some(csnp_interval) = csnp_interval {
+ fabric_section.properties.csnp_interval = Some(csnp_interval);
+ }
+
+ for property in delete {
+ match property {
+ FabricDeletableProperties::IpPrefix => {
+ fabric_section.ip_prefix = None;
+ }
+ FabricDeletableProperties::Ip6Prefix => {
+ fabric_section.ip6_prefix = None;
+ }
+ FabricDeletableProperties::Protocol(
+ IsisDeletableProperties::CsnpInterval,
+ ) => fabric_section.properties.csnp_interval = None,
+ FabricDeletableProperties::Protocol(
+ IsisDeletableProperties::HelloInterval,
+ ) => fabric_section.properties.hello_interval = None,
+ }
+ }
+
+ Ok(())
+ }
(Fabric::Ospf(fabric_section), FabricUpdater::Ospf(updater)) => {
let FabricSectionUpdater::<OspfPropertiesUpdater, OspfDeletableProperties> {
ip_prefix,
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
index 38911a624740..c8657f2a5dfa 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
@@ -8,6 +8,9 @@ use proxmox_schema::{
};
use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::protocol::isis::{
+ IsisDeletableProperties, IsisProperties, IsisPropertiesUpdater,
+};
use crate::sdn::fabric::section_config::protocol::openfabric::{
OpenfabricDeletableProperties, OpenfabricProperties, OpenfabricPropertiesUpdater,
};
@@ -135,6 +138,10 @@ impl UpdaterType for FabricSection<OpenfabricProperties> {
type Updater = FabricSectionUpdater<OpenfabricPropertiesUpdater, OpenfabricDeletableProperties>;
}
+impl UpdaterType for FabricSection<IsisProperties> {
+ type Updater = FabricSectionUpdater<IsisPropertiesUpdater, IsisDeletableProperties>;
+}
+
impl UpdaterType for FabricSection<OspfProperties> {
type Updater = FabricSectionUpdater<OspfPropertiesUpdater, OspfDeletableProperties>;
}
@@ -158,6 +165,7 @@ impl UpdaterType for FabricSection<OspfProperties> {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Fabric {
Openfabric(FabricSection<OpenfabricProperties>),
+ Isis(FabricSection<IsisProperties>),
Ospf(FabricSection<OspfProperties>),
}
@@ -172,6 +180,7 @@ impl Fabric {
pub fn id(&self) -> &FabricId {
match self {
Self::Openfabric(fabric_section) => fabric_section.id(),
+ Self::Isis(fabric_section) => fabric_section.id(),
Self::Ospf(fabric_section) => fabric_section.id(),
}
}
@@ -182,6 +191,7 @@ impl Fabric {
pub fn ip_prefix(&self) -> Option<Ipv4Cidr> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix(),
+ Fabric::Isis(fabric_section) => fabric_section.ip_prefix(),
Fabric::Ospf(fabric_section) => fabric_section.ip_prefix(),
}
}
@@ -192,6 +202,7 @@ impl Fabric {
pub fn set_ip_prefix(&mut self, ipv4_cidr: Ipv4Cidr) {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
+ Fabric::Isis(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
Fabric::Ospf(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
}
}
@@ -202,6 +213,7 @@ impl Fabric {
pub fn ip6_prefix(&self) -> Option<Ipv6Cidr> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix(),
+ Fabric::Isis(fabric_section) => fabric_section.ip6_prefix(),
Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix(),
}
}
@@ -212,6 +224,7 @@ impl Fabric {
pub fn set_ip6_prefix(&mut self, ipv6_cidr: Ipv6Cidr) {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
+ Fabric::Isis(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
}
}
@@ -224,6 +237,7 @@ impl Validatable for Fabric {
fn validate(&self) -> Result<(), Self::Error> {
match self {
Fabric::Openfabric(fabric_section) => fabric_section.validate(),
+ Fabric::Isis(fabric_section) => fabric_section.validate(),
Fabric::Ospf(fabric_section) => fabric_section.validate(),
}
}
@@ -235,6 +249,12 @@ impl From<FabricSection<OpenfabricProperties>> for Fabric {
}
}
+impl From<FabricSection<IsisProperties>> for Fabric {
+ fn from(section: FabricSection<IsisProperties>) -> Self {
+ Fabric::Isis(section)
+ }
+}
+
impl From<FabricSection<OspfProperties>> for Fabric {
fn from(section: FabricSection<OspfProperties>) -> Self {
Fabric::Ospf(section)
@@ -246,6 +266,7 @@ impl From<FabricSection<OspfProperties>> for Fabric {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum FabricUpdater {
Openfabric(<FabricSection<OpenfabricProperties> as UpdaterType>::Updater),
+ Isis(<FabricSection<IsisProperties> as UpdaterType>::Updater),
Ospf(<FabricSection<OspfProperties> as UpdaterType>::Updater),
}
@@ -253,6 +274,7 @@ impl Updater for FabricUpdater {
fn is_empty(&self) -> bool {
match self {
FabricUpdater::Openfabric(updater) => updater.is_empty(),
+ FabricUpdater::Isis(updater) => updater.is_empty(),
FabricUpdater::Ospf(updater) => updater.is_empty(),
}
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
index d02d4ae96437..f4621159dbe5 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
@@ -10,6 +10,7 @@ use crate::sdn::fabric::section_config::{
fabric::{Fabric, FabricSection, FABRIC_ID_REGEX_STR},
node::{Node, NodeSection, NODE_ID_REGEX_STR},
protocol::{
+ isis::{IsisNodeProperties, IsisProperties},
openfabric::{OpenfabricNodeProperties, OpenfabricProperties},
ospf::{OspfNodeProperties, OspfProperties},
},
@@ -30,8 +31,10 @@ impl From<Section> for FabricOrNode<Fabric, Node> {
fn from(section: Section) -> Self {
match section {
Section::OpenfabricFabric(fabric_section) => Self::Fabric(fabric_section.into()),
+ Section::IsisFabric(fabric_section) => Self::Fabric(fabric_section.into()),
Section::OspfFabric(fabric_section) => Self::Fabric(fabric_section.into()),
Section::OpenfabricNode(node_section) => Self::Node(node_section.into()),
+ Section::IsisNode(node_section) => Self::Node(node_section.into()),
Section::OspfNode(node_section) => Self::Node(node_section.into()),
}
}
@@ -61,8 +64,10 @@ pub const SECTION_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SECTION
#[serde(rename_all = "snake_case", tag = "type")]
pub enum Section {
OpenfabricFabric(FabricSection<OpenfabricProperties>),
+ IsisFabric(FabricSection<IsisProperties>),
OspfFabric(FabricSection<OspfProperties>),
OpenfabricNode(NodeSection<OpenfabricNodeProperties>),
+ IsisNode(NodeSection<IsisNodeProperties>),
OspfNode(NodeSection<OspfNodeProperties>),
}
@@ -72,6 +77,12 @@ impl From<FabricSection<OpenfabricProperties>> for Section {
}
}
+impl From<FabricSection<IsisProperties>> for Section {
+ fn from(section: FabricSection<IsisProperties>) -> Self {
+ Self::IsisFabric(section)
+ }
+}
+
impl From<FabricSection<OspfProperties>> for Section {
fn from(section: FabricSection<OspfProperties>) -> Self {
Self::OspfFabric(section)
@@ -84,6 +95,12 @@ impl From<NodeSection<OpenfabricNodeProperties>> for Section {
}
}
+impl From<NodeSection<IsisNodeProperties>> for Section {
+ fn from(section: NodeSection<IsisNodeProperties>) -> Self {
+ Self::IsisNode(section)
+ }
+}
+
impl From<NodeSection<OspfNodeProperties>> for Section {
fn from(section: NodeSection<OspfNodeProperties>) -> Self {
Self::OspfNode(section)
@@ -94,6 +111,7 @@ impl From<Fabric> for Section {
fn from(fabric: Fabric) -> Self {
match fabric {
Fabric::Openfabric(fabric_section) => fabric_section.into(),
+ Fabric::Isis(fabric_section) => fabric_section.into(),
Fabric::Ospf(fabric_section) => fabric_section.into(),
}
}
@@ -103,6 +121,7 @@ impl From<Node> for Section {
fn from(node: Node) -> Self {
match node {
Node::Openfabric(node_section) => node_section.into(),
+ Node::Isis(node_section) => node_section.into(),
Node::Ospf(node_section) => node_section.into(),
}
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
index 17d2f0b8de8a..7410f47835a1 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
@@ -10,6 +10,7 @@ use proxmox_schema::{
};
use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::protocol::isis::IsisNodeProperties;
use crate::sdn::fabric::section_config::{
fabric::{FabricId, FABRIC_ID_REGEX_STR},
protocol::{openfabric::OpenfabricNodeProperties, ospf::OspfNodeProperties},
@@ -185,6 +186,7 @@ impl<T: ApiType> ApiType for NodeSection<T> {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Node {
Openfabric(NodeSection<OpenfabricNodeProperties>),
+ Isis(NodeSection<IsisNodeProperties>),
Ospf(NodeSection<OspfNodeProperties>),
}
@@ -193,6 +195,7 @@ impl Node {
pub fn id(&self) -> &NodeSectionId {
match self {
Node::Openfabric(node_section) => node_section.id(),
+ Node::Isis(node_section) => node_section.id(),
Node::Ospf(node_section) => node_section.id(),
}
}
@@ -201,6 +204,7 @@ impl Node {
pub fn ip(&self) -> Option<std::net::Ipv4Addr> {
match self {
Node::Openfabric(node_section) => node_section.ip(),
+ Node::Isis(node_section) => node_section.ip(),
Node::Ospf(node_section) => node_section.ip(),
}
}
@@ -209,6 +213,7 @@ impl Node {
pub fn ip6(&self) -> Option<std::net::Ipv6Addr> {
match self {
Node::Openfabric(node_section) => node_section.ip6(),
+ Node::Isis(node_section) => node_section.ip6(),
Node::Ospf(node_section) => node_section.ip6(),
}
}
@@ -220,6 +225,7 @@ impl Validatable for Node {
fn validate(&self) -> Result<(), Self::Error> {
match self {
Node::Openfabric(node_section) => node_section.validate(),
+ Node::Isis(node_section) => node_section.validate(),
Node::Ospf(node_section) => node_section.validate(),
}
}
@@ -231,6 +237,12 @@ impl From<NodeSection<OpenfabricNodeProperties>> for Node {
}
}
+impl From<NodeSection<IsisNodeProperties>> for Node {
+ fn from(value: NodeSection<IsisNodeProperties>) -> Self {
+ Self::Isis(value)
+ }
+}
+
impl From<NodeSection<OspfNodeProperties>> for Node {
fn from(value: NodeSection<OspfNodeProperties>) -> Self {
Self::Ospf(value)
@@ -258,6 +270,7 @@ pub mod api {
use proxmox_schema::{Updater, UpdaterType};
use crate::sdn::fabric::section_config::protocol::{
+ isis::{IsisNodeDeletableProperties, IsisNodePropertiesUpdater},
openfabric::{
OpenfabricNodeDeletableProperties, OpenfabricNodeProperties,
OpenfabricNodePropertiesUpdater,
@@ -319,6 +332,7 @@ pub mod api {
#[serde(rename_all = "snake_case", tag = "protocol")]
pub enum Node {
Openfabric(NodeData<OpenfabricNodeProperties>),
+ Isis(NodeData<IsisNodeProperties>),
Ospf(NodeData<OspfNodeProperties>),
}
@@ -326,6 +340,7 @@ pub mod api {
fn from(value: super::Node) -> Self {
match value {
super::Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
+ super::Node::Isis(node_section) => Self::Isis(node_section.into()),
super::Node::Ospf(node_section) => Self::Ospf(node_section.into()),
}
}
@@ -335,6 +350,7 @@ pub mod api {
fn from(value: Node) -> Self {
match value {
Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
+ Node::Isis(node_section) => Self::Isis(node_section.into()),
Node::Ospf(node_section) => Self::Ospf(node_section.into()),
}
}
@@ -345,6 +361,10 @@ pub mod api {
NodeDataUpdater<OpenfabricNodePropertiesUpdater, OpenfabricNodeDeletableProperties>;
}
+ impl UpdaterType for NodeData<IsisNodeProperties> {
+ type Updater = NodeDataUpdater<IsisNodePropertiesUpdater, IsisNodeDeletableProperties>;
+ }
+
impl UpdaterType for NodeData<OspfNodeProperties> {
type Updater = NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>;
}
@@ -383,6 +403,7 @@ pub mod api {
Openfabric(
NodeDataUpdater<OpenfabricNodePropertiesUpdater, OpenfabricNodeDeletableProperties>,
),
+ Isis(NodeDataUpdater<IsisNodePropertiesUpdater, IsisNodeDeletableProperties>),
Ospf(NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>),
}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
new file mode 100644
index 000000000000..3d110b06308f
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/isis.rs
@@ -0,0 +1,151 @@
+use std::ops::{Deref, DerefMut};
+
+use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr};
+use serde::{Deserialize, Serialize};
+
+use proxmox_schema::{api, property_string::PropertyString, ApiStringFormat, Updater};
+use proxmox_sdn_types::openfabric::{CsnpInterval, HelloInterval, HelloMultiplier};
+
+use crate::common::valid::Validatable;
+use crate::sdn::fabric::section_config::fabric::FabricSection;
+use crate::sdn::fabric::section_config::interface::InterfaceName;
+use crate::sdn::fabric::section_config::node::NodeSection;
+use crate::sdn::fabric::FabricConfigError;
+
+/// Protocol-specific options for an IS-IS Fabric.
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisProperties {
+ /// This will be distributed to all interfaces on every node. The Hello Interval for a given
+ /// interface in seconds. The range is 1 to 600. Hello packets are used to establish and
+ /// maintain adjacency between IS-IS neighbors.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) hello_interval: Option<HelloInterval>,
+
+ /// This will be distributed to all interfaces on every node.The Complete Sequence Number
+ /// Packets (CSNP) interval in seconds. The interval range is 1 to 600.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) csnp_interval: Option<CsnpInterval>,
+}
+
+impl Validatable for FabricSection<IsisProperties> {
+ type Error = FabricConfigError;
+
+ /// Validates the [`FabricSection<IsisProperties>`].
+ ///
+ /// Checks if we have either IPv4-prefix or IPv6-prefix. If both are not set, return an error.
+ fn validate(&self) -> Result<(), Self::Error> {
+ if self.ip_prefix().is_none() && self.ip6_prefix().is_none() {
+ return Err(FabricConfigError::FabricNoIpPrefix(self.id().to_string()));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
+#[serde(rename_all = "snake_case")]
+pub enum IsisDeletableProperties {
+ HelloInterval,
+ CsnpInterval,
+}
+
+/// Properties for an IS-IS node
+#[api(
+ properties: {
+ interfaces: {
+ type: Array,
+ optional: true,
+ items: {
+ type: String,
+ description: "IS-IS interface",
+ format: &ApiStringFormat::PropertyString(&IsisInterfaceProperties::API_SCHEMA),
+ }
+ },
+ }
+)]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisNodeProperties {
+ /// Interfaces for this node
+ #[serde(default)]
+ pub(crate) interfaces: Vec<PropertyString<IsisInterfaceProperties>>,
+}
+
+impl IsisNodeProperties {
+ /// Returns an iterator over all the interfaces.
+ pub fn interfaces(&self) -> impl Iterator<Item = &IsisInterfaceProperties> {
+ self.interfaces
+ .iter()
+ .map(|property_string| property_string.deref())
+ }
+
+ /// Returns an iterator over all the interfaces (mutable).
+ pub fn interfaces_mut(&mut self) -> impl Iterator<Item = &mut IsisInterfaceProperties> {
+ self.interfaces
+ .iter_mut()
+ .map(|property_string| property_string.deref_mut())
+ }
+}
+
+impl Validatable for NodeSection<IsisNodeProperties> {
+ type Error = FabricConfigError;
+
+ /// Validates the [`FabricSection<IsisProperties>`].
+ ///
+ /// Checks if we have either an IPv4 or an IPv6 address. If neither is set, return an error.
+ fn validate(&self) -> Result<(), Self::Error> {
+ if self.ip().is_none() && self.ip6().is_none() {
+ return Err(FabricConfigError::NodeNoIp(self.id().to_string()));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum IsisNodeDeletableProperties {
+ Interfaces,
+}
+
+/// Properties for an IS-IS interface
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize, Updater, Hash)]
+pub struct IsisInterfaceProperties {
+ pub(crate) name: InterfaceName,
+
+ /// The multiplier for the hello holding time on a given interface. The range is 2 to
+ /// 100.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) hello_multiplier: Option<HelloMultiplier>,
+
+ /// If ip and ip6 are unset, then this is an point-to-point interface
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) ip: Option<Ipv4Cidr>,
+
+ /// If ip6 and ip are unset, then this is an point-to-point interface
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub(crate) ip6: Option<Ipv6Cidr>,
+}
+
+impl IsisInterfaceProperties {
+ /// Get the name of the interface.
+ pub fn name(&self) -> &InterfaceName {
+ &self.name
+ }
+
+ /// Set the name of the interface.
+ pub fn set_name(&mut self, name: InterfaceName) {
+ self.name = name
+ }
+
+ /// Get the IPv4 of the interface.
+ pub fn ip(&self) -> Option<Ipv4Cidr> {
+ self.ip
+ }
+
+ /// Get the IPv6 of the interface.
+ pub fn ip6(&self) -> Option<Ipv6Cidr> {
+ self.ip6
+ }
+}
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
index c1ec847ffbc3..2edd9f09df67 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
@@ -1,2 +1,3 @@
+pub mod isis;
pub mod openfabric;
pub mod ospf;
--
2.47.2
More information about the pve-devel
mailing list