* [pve-devel] [PATCH proxmox-ve-rs 1/3] frr: add IS-IS frr configuration types
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 2/3] ve-config: add IS-IS fabric config parsing and frr config generation Gabriel Goller
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Add types to generate the IS-IS frr configuration.
These are very similar to the OpenFabric configuration, but we want to
keep them separate because they will diverge in the future.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-frr/src/isis.rs | 90 +++++++++++++++++++++++++++++++++++
proxmox-frr/src/lib.rs | 13 +++++
proxmox-frr/src/route_map.rs | 2 +
proxmox-frr/src/serializer.rs | 37 ++++++++++++++
4 files changed, 142 insertions(+)
create mode 100644 proxmox-frr/src/isis.rs
diff --git a/proxmox-frr/src/isis.rs b/proxmox-frr/src/isis.rs
new file mode 100644
index 000000000000..a407c900ae58
--- /dev/null
+++ b/proxmox-frr/src/isis.rs
@@ -0,0 +1,90 @@
+use std::fmt::Debug;
+use std::fmt::Display;
+
+use proxmox_sdn_types::net::Net;
+
+use thiserror::Error;
+
+use crate::FrrWord;
+use crate::FrrWordError;
+
+/// The name of a IS-IS router. Is an FrrWord.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisRouterName(FrrWord);
+
+impl From<FrrWord> for IsisRouterName {
+ fn from(value: FrrWord) -> Self {
+ Self(value)
+ }
+}
+
+impl IsisRouterName {
+ pub fn new(name: FrrWord) -> Self {
+ Self(name)
+ }
+}
+
+impl Display for IsisRouterName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "isis {}", self.0)
+ }
+}
+
+/// All the properties a IS-IS router can hold.
+///
+/// These can serialized with a " " space prefix as they are in the `router isis` block.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisRouter {
+ /// The NET address
+ pub net: Net,
+}
+
+impl IsisRouter {
+ pub fn new(net: Net) -> Self {
+ Self { net }
+ }
+
+ pub fn net(&self) -> &Net {
+ &self.net
+ }
+}
+
+/// The IS-IS interface properties.
+///
+/// This struct holds all the IS-IS interface properties. The most important one here is the
+/// fabric_id, which ties the interface to a fabric. When serialized these properties all get
+/// prefixed with a space (" ") as they are inside the interface block. They serialize roughly to:
+///
+/// ```text
+/// interface ens20
+/// ip router isis <fabric_id>
+/// ipv6 router isis <fabric_id>
+/// isis hello-interval <value>
+/// isis hello-multiplier <value>
+/// isis csnp-interval <value>
+/// isis passive <value>
+/// ```
+///
+/// The is_ipv4 and is_ipv6 properties decide if we need to add `ip router isis`, `ipv6
+/// router isis`, or both. An interface can only be part of a single fabric.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct IsisInterface {
+ // Note: an interface can only be a part of a single fabric (so no vec needed here)
+ pub fabric_id: IsisRouterName,
+ pub passive: Option<bool>,
+ // Note: openfabric is very similar to isis, so we can use the same properties here
+ pub hello_interval: Option<proxmox_sdn_types::openfabric::HelloInterval>,
+ pub csnp_interval: Option<proxmox_sdn_types::openfabric::CsnpInterval>,
+ pub hello_multiplier: Option<proxmox_sdn_types::openfabric::HelloMultiplier>,
+ pub point_to_point: bool,
+ pub is_ipv4: bool,
+ pub is_ipv6: bool,
+}
+
+#[derive(Error, Debug)]
+pub enum IsisInterfaceError {
+ #[error("Unknown error converting to IsisInterface")]
+ UnknownError,
+ #[error("Error parsing frr word")]
+ FrrWordParse(#[from] FrrWordError),
+}
diff --git a/proxmox-frr/src/lib.rs b/proxmox-frr/src/lib.rs
index 86101182fafd..daf592e0ad7f 100644
--- a/proxmox-frr/src/lib.rs
+++ b/proxmox-frr/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod isis;
pub mod openfabric;
pub mod ospf;
pub mod route_map;
@@ -25,6 +26,7 @@ use thiserror::Error;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Router {
Openfabric(openfabric::OpenfabricRouter),
+ Isis(isis::IsisRouter),
Ospf(ospf::OspfRouter),
}
@@ -41,6 +43,7 @@ impl From<openfabric::OpenfabricRouter> for Router {
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RouterName {
Openfabric(openfabric::OpenfabricRouterName),
+ Isis(isis::IsisRouterName),
Ospf(ospf::OspfRouterName),
}
@@ -54,6 +57,7 @@ impl Display for RouterName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Openfabric(r) => r.fmt(f),
+ Self::Isis(r) => r.fmt(f),
Self::Ospf(r) => r.fmt(f),
}
}
@@ -65,6 +69,7 @@ impl Display for RouterName {
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum InterfaceName {
Openfabric(CommonInterfaceName),
+ Isis(CommonInterfaceName),
Ospf(CommonInterfaceName),
}
@@ -72,6 +77,7 @@ impl Display for InterfaceName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InterfaceName::Openfabric(frr_word) => frr_word.fmt(f),
+ InterfaceName::Isis(frr_word) => frr_word.fmt(f),
InterfaceName::Ospf(frr_word) => frr_word.fmt(f),
}
}
@@ -86,6 +92,7 @@ impl Display for InterfaceName {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Interface {
Openfabric(openfabric::OpenfabricInterface),
+ Isis(isis::IsisInterface),
Ospf(ospf::OspfInterface),
}
@@ -95,6 +102,12 @@ impl From<openfabric::OpenfabricInterface> for Interface {
}
}
+impl From<isis::IsisInterface> for Interface {
+ fn from(value: isis::IsisInterface) -> Self {
+ Self::Isis(value)
+ }
+}
+
impl From<ospf::OspfInterface> for Interface {
fn from(value: ospf::OspfInterface) -> Self {
Self::Ospf(value)
diff --git a/proxmox-frr/src/route_map.rs b/proxmox-frr/src/route_map.rs
index 0918a3cead14..4e163a912425 100644
--- a/proxmox-frr/src/route_map.rs
+++ b/proxmox-frr/src/route_map.rs
@@ -201,6 +201,7 @@ pub struct RouteMap {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ProtocolType {
Openfabric,
+ Isis,
Ospf,
}
@@ -208,6 +209,7 @@ impl Display for ProtocolType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProtocolType::Openfabric => write!(f, "openfabric"),
+ ProtocolType::Isis => write!(f, "isis"),
ProtocolType::Ospf => write!(f, "ospf"),
}
}
diff --git a/proxmox-frr/src/serializer.rs b/proxmox-frr/src/serializer.rs
index f8a3c7238d94..794c43db8888 100644
--- a/proxmox-frr/src/serializer.rs
+++ b/proxmox-frr/src/serializer.rs
@@ -1,6 +1,7 @@
use std::fmt::{self, Write};
use crate::{
+ isis::{IsisInterface, IsisRouter},
openfabric::{OpenfabricInterface, OpenfabricRouter},
ospf::{OspfInterface, OspfRouter},
route_map::{AccessList, AccessListName, ProtocolRouteMap, RouteMap},
@@ -84,6 +85,7 @@ impl FrrSerializer for Interface {
fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
match self {
Interface::Openfabric(openfabric_interface) => openfabric_interface.serialize(f)?,
+ Interface::Isis(isis_interface) => isis_interface.serialize(f)?,
Interface::Ospf(ospf_interface) => ospf_interface.serialize(f)?,
}
Ok(())
@@ -114,6 +116,33 @@ impl FrrSerializer for OpenfabricInterface {
}
}
+impl FrrSerializer for IsisInterface {
+ fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
+ if self.is_ipv6 {
+ writeln!(f, " ipv6 router {}", self.fabric_id)?;
+ }
+ if self.is_ipv4 {
+ writeln!(f, " ip router {}", self.fabric_id)?;
+ }
+ if self.passive == Some(true) {
+ writeln!(f, " isis passive")?;
+ }
+ if let Some(interval) = self.hello_interval {
+ writeln!(f, " isis hello-interval {interval}",)?;
+ }
+ if let Some(multiplier) = self.hello_multiplier {
+ writeln!(f, " isis hello-multiplier {multiplier}",)?;
+ }
+ if let Some(interval) = self.csnp_interval {
+ writeln!(f, " isis csnp-interval {interval}",)?;
+ }
+ if self.point_to_point {
+ writeln!(f, " isis network point-to-point")?;
+ }
+ Ok(())
+ }
+}
+
impl FrrSerializer for OspfInterface {
fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
writeln!(f, " ip ospf {}", self.area)?;
@@ -131,6 +160,7 @@ impl FrrSerializer for Router {
fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
match self {
Router::Openfabric(open_fabric_router) => open_fabric_router.serialize(f),
+ Router::Isis(isis_router) => isis_router.serialize(f),
Router::Ospf(ospf_router) => ospf_router.serialize(f),
}
}
@@ -143,6 +173,13 @@ impl FrrSerializer for OpenfabricRouter {
}
}
+impl FrrSerializer for IsisRouter {
+ fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
+ writeln!(f, " net {}", self.net())?;
+ Ok(())
+ }
+}
+
impl FrrSerializer for OspfRouter {
fn serialize(&self, f: &mut FrrConfigBlob<'_>) -> fmt::Result {
writeln!(f, " ospf router-id {}", self.router_id())?;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH proxmox-ve-rs 2/3] ve-config: add IS-IS fabric config parsing and frr config generation
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 1/3] frr: add IS-IS frr configuration types Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 3/3] ve-config: add integration tests for IS-IS fabrics Gabriel Goller
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
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@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
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH proxmox-ve-rs 3/3] ve-config: add integration tests for IS-IS fabrics
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 1/3] frr: add IS-IS frr configuration types Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 2/3] ve-config: add IS-IS fabric config parsing and frr config generation Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-perl-rs 1/1] pve-rs: fabrics: add IS-IS protocol ifupdown config generation Gabriel Goller
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Add three integration tests for the IS-IS fabrics: a simple one, one
with dualstack ipv6 and ipv4, and one with ipv6 only.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
.../tests/fabric/cfg/isis_default/fabrics.cfg | 18 +++++++
.../fabric/cfg/isis_dualstack/fabrics.cfg | 22 ++++++++
.../fabric/cfg/isis_ipv6_only/fabrics.cfg | 18 +++++++
proxmox-ve-config/tests/fabric/main.rs | 51 +++++++++++++++++++
.../snapshots/fabric__isis_default_pve.snap | 36 +++++++++++++
.../snapshots/fabric__isis_default_pve1.snap | 35 +++++++++++++
.../snapshots/fabric__isis_dualstack_pve.snap | 48 +++++++++++++++++
.../snapshots/fabric__isis_ipv6_only_pve.snap | 36 +++++++++++++
8 files changed, 264 insertions(+)
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
create mode 100644 proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
new file mode 100644
index 000000000000..161831977bc6
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_default/fabrics.cfg
@@ -0,0 +1,18 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip_prefix 192.168.2.0/24
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip 192.168.2.8
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.9
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.10
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
new file mode 100644
index 000000000000..c8a0458d2e88
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_dualstack/fabrics.cfg
@@ -0,0 +1,22 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip_prefix 192.168.2.0/24
+ ip6_prefix 2001:db8::0/64
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip 192.168.2.8
+ ip6 2001:db8::1
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.9
+ ip6 2001:db8::2
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip 192.168.2.10
+ ip6 2001:db8::3
diff --git a/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg b/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
new file mode 100644
index 000000000000..584ecbbc4ef9
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/cfg/isis_ipv6_only/fabrics.cfg
@@ -0,0 +1,18 @@
+isis_fabric: uwu
+ hello_interval 4
+ ip6_prefix a:b::0/75
+
+isis_node: uwu_pve
+ interfaces name=ens20,hello_multiplier=50
+ interfaces name=ens19
+ ip6 a:b::a
+
+isis_node: uwu_pve1
+ interfaces name=ens19
+ interfaces name=ens20
+ ip6 a:b::b
+
+isis_node: uwu_pve2
+ interfaces name=ens19
+ interfaces name=ens20
+ ip6 a:b::c
diff --git a/proxmox-ve-config/tests/fabric/main.rs b/proxmox-ve-config/tests/fabric/main.rs
index 47bbbeb77886..277982c7b07e 100644
--- a/proxmox-ve-config/tests/fabric/main.rs
+++ b/proxmox-ve-config/tests/fabric/main.rs
@@ -37,6 +37,29 @@ fn openfabric_default() {
insta::assert_snapshot!(helper::reference_name!("pve1"), output);
}
+#[test]
+fn isis_default() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let mut frr_config = FrrConfigBuilder::default()
+ .add_fabrics(config.clone())
+ .build(NodeId::from_string("pve".to_owned()).expect("invalid nodeid"))
+ .expect("error building frr config");
+
+ let mut output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+
+ frr_config = FrrConfigBuilder::default()
+ .add_fabrics(config.clone())
+ .build(NodeId::from_string("pve1".to_owned()).expect("invalid nodeid"))
+ .expect("error building frr config");
+
+ output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve1"), output);
+}
+
#[test]
fn ospf_default() {
let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
@@ -126,6 +149,20 @@ fn openfabric_dualstack() {
insta::assert_snapshot!(helper::reference_name!("pve"), output);
}
+#[test]
+fn isis_dualstack() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let frr_config = FrrConfigBuilder::default()
+ .add_fabrics(config)
+ .build(NodeId::from_string("pve".to_owned()).expect("invalid nodeid"))
+ .expect("error building frr config");
+
+ let output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+}
+
#[test]
fn openfabric_ipv6_only() {
let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
@@ -139,3 +176,17 @@ fn openfabric_ipv6_only() {
insta::assert_snapshot!(helper::reference_name!("pve"), output);
}
+
+#[test]
+fn isis_ipv6_only() {
+ let config = FabricConfig::parse_section_config(helper::get_fabrics_config!()).unwrap();
+
+ let frr_config = FrrConfigBuilder::default()
+ .add_fabrics(config)
+ .build(NodeId::from_string("pve".to_owned()).expect("invalid nodeid"))
+ .expect("error building frr config");
+
+ let output = dump(&frr_config).expect("error dumping stuff");
+
+ insta::assert_snapshot!(helper::reference_name!("pve"), output);
+}
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
new file mode 100644
index 000000000000..1001fb50ba58
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve.snap
@@ -0,0 +1,36 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+router isis uwu
+ net 49.0001.1921.6800.2008.00
+exit
+!
+interface dummy_uwu
+ ip router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ip router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.8
+exit
+!
+ip protocol isis route-map pve_isis
+!
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
new file mode 100644
index 000000000000..c50d7896f77e
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_default_pve1.snap
@@ -0,0 +1,35 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+router isis uwu
+ net 49.0001.1921.6800.2009.00
+exit
+!
+interface dummy_uwu
+ ip router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.9
+exit
+!
+ip protocol isis route-map pve_isis
+!
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
new file mode 100644
index 000000000000..ae4292bd7f8d
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_dualstack_pve.snap
@@ -0,0 +1,48 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+router isis uwu
+ net 49.0001.1921.6800.2008.00
+exit
+!
+interface dummy_uwu
+ ipv6 router isis uwu
+ ip router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ipv6 router isis uwu
+ ip router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ipv6 router isis uwu
+ ip router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+access-list pve_isis_uwu_ips permit 192.168.2.0/24
+!
+ipv6 access-list pve_isis_uwu_ip6s permit 2001:db8::/64
+!
+route-map pve_isis permit 100
+ match ip address pve_isis_uwu_ips
+ set src 192.168.2.8
+exit
+!
+route-map pve_isis6 permit 110
+ match ipv6 address pve_isis_uwu_ip6s
+ set src 2001:db8::1
+exit
+!
+ip protocol isis route-map pve_isis
+!
+ipv6 protocol isis route-map pve_isis6
+!
diff --git a/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
new file mode 100644
index 000000000000..7b5416490f5a
--- /dev/null
+++ b/proxmox-ve-config/tests/fabric/snapshots/fabric__isis_ipv6_only_pve.snap
@@ -0,0 +1,36 @@
+---
+source: proxmox-ve-config/tests/fabric/main.rs
+expression: output
+snapshot_kind: text
+---
+router isis uwu
+ net 49.0001.0000.0000.000a.00
+exit
+!
+interface dummy_uwu
+ ipv6 router isis uwu
+ isis passive
+exit
+!
+interface ens19
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis network point-to-point
+exit
+!
+interface ens20
+ ipv6 router isis uwu
+ isis hello-interval 4
+ isis hello-multiplier 50
+ isis network point-to-point
+exit
+!
+ipv6 access-list pve_isis_uwu_ip6s permit a:b::/75
+!
+route-map pve_isis6 permit 100
+ match ipv6 address pve_isis_uwu_ip6s
+ set src a:b::a
+exit
+!
+ipv6 protocol isis route-map pve_isis6
+!
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs 1/1] pve-rs: fabrics: add IS-IS protocol ifupdown config generation
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (2 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-ve-rs 3/3] ve-config: add integration tests for IS-IS fabrics Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-manager 1/2] fabrics: add IS-IS panels Gabriel Goller
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Generate the interfaces that have been configured with IS-IS to the
ifupdown config (enable ip-forwarding on all). IS-IS needs to have the
ip-address duplicated from the dummy interface to every interface in
unnumbered mode (like with ospf). This could potentially be fixed in the
future with [0].
[0]: https://github.com/FRRouting/frr/pull/19429
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pve-rs/src/bindings/sdn/fabrics.rs | 57 +++++++++++++++++++++++++++++-
1 file changed, 56 insertions(+), 1 deletion(-)
diff --git a/pve-rs/src/bindings/sdn/fabrics.rs b/pve-rs/src/bindings/sdn/fabrics.rs
index 587b1d68c8fb..d345112de4f3 100644
--- a/pve-rs/src/bindings/sdn/fabrics.rs
+++ b/pve-rs/src/bindings/sdn/fabrics.rs
@@ -348,7 +348,14 @@ pub mod pve_rs_sdn_fabrics {
match node {
ConfigNode::Openfabric(node_section) => {
for interface in node_section.properties_mut().interfaces_mut() {
- if let Some(mapped_name) = map_name(&mapping, &interface.name())? {
+ if let Some(mapped_name) = map_name(&mapping, interface.name())? {
+ interface.set_name(mapped_name);
+ }
+ }
+ }
+ ConfigNode::Isis(node_section) => {
+ for interface in node_section.properties_mut().interfaces_mut() {
+ if let Some(mapped_name) = map_name(&mapping, interface.name())? {
interface.set_name(mapped_name);
}
}
@@ -452,6 +459,9 @@ pub mod pve_rs_sdn_fabrics {
FabricEntry::Openfabric(_) => {
daemons.insert("fabricd");
}
+ FabricEntry::Isis(_) => {
+ daemons.insert("isisd");
+ }
};
}
@@ -553,6 +563,51 @@ pub mod pve_rs_sdn_fabrics {
}
}
}
+ ConfigNode::Isis(node_section) => {
+ for interface in node_section.properties().interfaces() {
+ if interface.ip().is_some() || interface.ip6().is_some() {
+ if let Some(ip) = interface.ip() {
+ let interface =
+ render_interface(interface.name(), Cidr::from(ip), false)?;
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface}")?;
+ }
+ if let Some(ip) = interface.ip6() {
+ let interface =
+ render_interface(interface.name(), Cidr::from(ip), false)?;
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface}")?;
+ }
+ } else {
+ let interface_string: String;
+ if let Some(ip) = node.ip() {
+ interface_string = render_interface(
+ interface.name(),
+ Cidr::from(IpAddr::from(ip)),
+ false,
+ )?;
+ } else if let Some(ip6) = node.ip6() {
+ interface_string = render_interface(
+ interface.name(),
+ Cidr::from(IpAddr::from(ip6)),
+ false,
+ )?;
+ } else {
+ return Err(anyhow::anyhow!("there has to be a ipv4 address"));
+ }
+ writeln!(interfaces)?;
+ write!(interfaces, "{interface_string}")?;
+ }
+
+ // If not ip is configured, add auto and empty iface to bring interface up
+ if let (None, None) = (interface.ip(), interface.ip6()) {
+ writeln!(interfaces)?;
+ writeln!(interfaces, "auto {}", interface.name())?;
+ writeln!(interfaces, "iface {}", interface.name())?;
+ writeln!(interfaces, "\tip-forward 1")?;
+ }
+ }
+ }
ConfigNode::Ospf(node_section) => {
for interface in node_section.properties().interfaces() {
if let Some(ip) = interface.ip() {
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH pve-manager 1/2] fabrics: add IS-IS panels
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (3 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH proxmox-perl-rs 1/1] pve-rs: fabrics: add IS-IS protocol ifupdown config generation Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-manager 2/2] sdn: add warning about IS-IS controller deprecation Gabriel Goller
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
The IS-IS panels have the same properties as OpenFabric both on the
FabricEdit and the NodeEdit.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/Makefile | 19 ++---
www/manager6/sdn/FabricsView.js | 12 ++++
www/manager6/sdn/fabrics/NodeEdit.js | 1 +
www/manager6/sdn/fabrics/isis/FabricEdit.js | 69 +++++++++++++++++++
.../sdn/fabrics/isis/InterfacePanel.js | 34 +++++++++
www/manager6/sdn/fabrics/isis/NodeEdit.js | 22 ++++++
6 files changed, 149 insertions(+), 8 deletions(-)
create mode 100644 www/manager6/sdn/fabrics/isis/FabricEdit.js
create mode 100644 www/manager6/sdn/fabrics/isis/InterfacePanel.js
create mode 100644 www/manager6/sdn/fabrics/isis/NodeEdit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 07401f21520b..9e16bdef46bc 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -314,15 +314,18 @@ JSSRC= \
sdn/zones/VxlanEdit.js \
sdn/FabricsView.js \
sdn/fabrics/Common.js \
- sdn/fabrics/InterfacePanel.js \
+ sdn/fabrics/InterfacePanel.js \
sdn/fabrics/NodeEdit.js \
- sdn/fabrics/FabricEdit.js \
- sdn/fabrics/openfabric/InterfacePanel.js \
- sdn/fabrics/openfabric/NodeEdit.js \
- sdn/fabrics/openfabric/FabricEdit.js \
- sdn/fabrics/ospf/InterfacePanel.js \
- sdn/fabrics/ospf/NodeEdit.js \
- sdn/fabrics/ospf/FabricEdit.js \
+ sdn/fabrics/FabricEdit.js \
+ sdn/fabrics/openfabric/InterfacePanel.js \
+ sdn/fabrics/openfabric/NodeEdit.js \
+ sdn/fabrics/openfabric/FabricEdit.js \
+ sdn/fabrics/isis/InterfacePanel.js \
+ sdn/fabrics/isis/NodeEdit.js \
+ sdn/fabrics/isis/FabricEdit.js \
+ sdn/fabrics/ospf/InterfacePanel.js \
+ sdn/fabrics/ospf/NodeEdit.js \
+ sdn/fabrics/ospf/FabricEdit.js \
storage/ContentView.js \
storage/BackupView.js \
storage/Base.js \
diff --git a/www/manager6/sdn/FabricsView.js b/www/manager6/sdn/FabricsView.js
index 093a70f35d3c..dcedc214e184 100644
--- a/www/manager6/sdn/FabricsView.js
+++ b/www/manager6/sdn/FabricsView.js
@@ -32,6 +32,7 @@ Ext.define('PVE.sdn.Fabric.View', {
if (rec.data.type === 'fabric') {
const PROTOCOL_DISPLAY_NAMES = {
openfabric: 'OpenFabric',
+ isis: 'IS-IS',
ospf: 'OSPF',
};
const displayValue = PROTOCOL_DISPLAY_NAMES[value];
@@ -190,6 +191,10 @@ Ext.define('PVE.sdn.Fabric.View', {
text: 'OpenFabric',
handler: 'addOpenfabric',
},
+ {
+ text: 'IS-IS',
+ handler: 'addIsis',
+ },
{
text: 'OSPF',
handler: 'addOspf',
@@ -271,6 +276,7 @@ Ext.define('PVE.sdn.Fabric.View', {
getFabricEditPanel: function (protocol) {
const FABRIC_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.Fabric.Edit',
+ isis: 'PVE.sdn.Fabric.Isis.Fabric.Edit',
ospf: 'PVE.sdn.Fabric.Ospf.Fabric.Edit',
};
@@ -280,6 +286,7 @@ Ext.define('PVE.sdn.Fabric.View', {
getNodeEditPanel: function (protocol) {
const NODE_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.Node.Edit',
+ isis: 'PVE.sdn.Fabric.Isis.Node.Edit',
ospf: 'PVE.sdn.Fabric.Ospf.Node.Edit',
};
@@ -291,6 +298,11 @@ Ext.define('PVE.sdn.Fabric.View', {
me.openFabricAddWindow('openfabric');
},
+ addIsis: function () {
+ let me = this;
+ me.openFabricAddWindow('isis');
+ },
+
addOspf: function () {
let me = this;
me.openFabricAddWindow('ospf');
diff --git a/www/manager6/sdn/fabrics/NodeEdit.js b/www/manager6/sdn/fabrics/NodeEdit.js
index 161917ccd1e5..1a8ec9df1b4b 100644
--- a/www/manager6/sdn/fabrics/NodeEdit.js
+++ b/www/manager6/sdn/fabrics/NodeEdit.js
@@ -199,6 +199,7 @@ Ext.define('PVE.sdn.Fabric.Node.Edit', {
getInterfacePanel: function (protocol) {
const INTERFACE_PANELS = {
openfabric: 'PVE.sdn.Fabric.OpenFabric.InterfacePanel',
+ isis: 'PVE.sdn.Fabric.Isis.InterfacePanel',
ospf: 'PVE.sdn.Fabric.Ospf.InterfacePanel',
};
diff --git a/www/manager6/sdn/fabrics/isis/FabricEdit.js b/www/manager6/sdn/fabrics/isis/FabricEdit.js
new file mode 100644
index 000000000000..38825de0bd0f
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/FabricEdit.js
@@ -0,0 +1,69 @@
+Ext.define('PVE.sdn.Fabric.Isis.Fabric.Edit', {
+ extend: 'PVE.sdn.Fabric.Fabric.Edit',
+
+ subject: 'IS-IS',
+ onlineHelp: 'pvesdn_isis_fabric',
+
+ viewModel: {
+ data: {
+ showIpv6ForwardingHint: false,
+ },
+ },
+
+ extraRequestParams: {
+ protocol: 'isis',
+ },
+
+ additionalItems: [
+ {
+ xtype: 'displayfield',
+ value: 'To make IPv6 fabrics work, enable global IPv6 forwarding on all nodes. Click on the Help button for more details.',
+ bind: {
+ hidden: '{!showIpv6ForwardingHint}',
+ },
+ userCls: 'pmx-hint',
+ },
+ {
+ xtype: 'proxmoxtextfield',
+ fieldLabel: gettext('IPv6 Prefix'),
+ labelWidth: 120,
+ name: 'ip6_prefix',
+ allowBlank: true,
+ skipEmptyText: true,
+ cbind: {
+ disabled: '{!isCreate}',
+ deleteEmpty: '{!isCreate}',
+ },
+ listeners: {
+ change: function (textbox, value) {
+ let vm = textbox.up('window').getViewModel();
+ vm.set('showIpv6ForwardingHint', !!value);
+ },
+ },
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('Hello Interval'),
+ labelWidth: 120,
+ name: 'hello_interval',
+ allowBlank: true,
+ emptyText: '3',
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ fieldLabel: gettext('CSNP Interval'),
+ labelWidth: 120,
+ name: 'csnp_interval',
+ allowBlank: true,
+ emptyText: '10',
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ ],
+});
diff --git a/www/manager6/sdn/fabrics/isis/InterfacePanel.js b/www/manager6/sdn/fabrics/isis/InterfacePanel.js
new file mode 100644
index 000000000000..dc2c5b55eb6a
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/InterfacePanel.js
@@ -0,0 +1,34 @@
+Ext.define('PVE.sdn.Fabric.Isis.InterfacePanel', {
+ extend: 'PVE.sdn.Fabric.InterfacePanel',
+
+ additionalColumns: [
+ {
+ text: gettext('IPv6'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'ip6',
+ flex: 1,
+ widget: {
+ xtype: 'proxmoxtextfield',
+ isFormField: false,
+ bind: {
+ disabled: '{record.isDisabled}',
+ },
+ },
+ },
+ {
+ text: gettext('Hello Multiplier'),
+ xtype: 'widgetcolumn',
+ dataIndex: 'hello_multiplier',
+ flex: 1,
+ hidden: true,
+ widget: {
+ xtype: 'proxmoxintegerfield',
+ isFormField: false,
+ emptyText: '10',
+ bind: {
+ disabled: '{record.isDisabled}',
+ },
+ },
+ },
+ ],
+});
diff --git a/www/manager6/sdn/fabrics/isis/NodeEdit.js b/www/manager6/sdn/fabrics/isis/NodeEdit.js
new file mode 100644
index 000000000000..9878edcccb73
--- /dev/null
+++ b/www/manager6/sdn/fabrics/isis/NodeEdit.js
@@ -0,0 +1,22 @@
+Ext.define('PVE.sdn.Fabric.Isis.Node.Edit', {
+ extend: 'PVE.sdn.Fabric.Node.Edit',
+ protocol: 'isis',
+
+ extraRequestParams: {
+ protocol: 'isis',
+ },
+
+ additionalItems: [
+ {
+ xtype: 'proxmoxtextfield',
+ fieldLabel: gettext('IPv6'),
+ labelWidth: 120,
+ name: 'ip6',
+ allowBlank: true,
+ skipEmptyText: true,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ },
+ },
+ ],
+});
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH pve-manager 2/2] sdn: add warning about IS-IS controller deprecation
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (4 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH pve-manager 1/2] fabrics: add IS-IS panels Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-network 1/1] fabrics: add IS-IS api types Gabriel Goller
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Instead of the IS-IS controller the new IS-IS fabric should be used.
Every option in the IS-IS controller can also be set on the new IS-IS
fabric. Nevertheless they can both be used simultaniously and there is
not yet a clear roadmap of when we are going to deprecate the IS-IS
controller.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
www/manager6/sdn/controllers/IsisEdit.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/www/manager6/sdn/controllers/IsisEdit.js b/www/manager6/sdn/controllers/IsisEdit.js
index edacd1736bba..f075fd7cdc1a 100644
--- a/www/manager6/sdn/controllers/IsisEdit.js
+++ b/www/manager6/sdn/controllers/IsisEdit.js
@@ -20,6 +20,11 @@ Ext.define('PVE.sdn.controllers.IsisInputPanel', {
var me = this;
me.items = [
+ {
+ xtype: 'displayfield',
+ value: 'Consider using the new IS-IS fabrics in the "Fabrics" in the sidebar. The "isis" controller will be deprecated soon.',
+ userCls: 'pmx-hint',
+ },
{
xtype: 'pveNodeSelector',
name: 'node',
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH pve-network 1/1] fabrics: add IS-IS api types
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (5 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH pve-manager 2/2] sdn: add warning about IS-IS controller deprecation Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-docs 1/1] sdn: add section about IS-IS fabric Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-gui-tests 1/1] fabrics: add screenshots for IS-IS fabric and nodes Gabriel Goller
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Add IS-IS api-types and add it to the protocol enum. Also make 'isisd' a
frr daemon managed by pve (This means we can enable and disable it).
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/Network/SDN/Fabrics.pm | 47 +++++++++++++++++++++++++++++-----
src/PVE/Network/SDN/Frr.pm | 1 +
2 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/src/PVE/Network/SDN/Fabrics.pm b/src/PVE/Network/SDN/Fabrics.pm
index d90992a7eceb..63f4f1497117 100644
--- a/src/PVE/Network/SDN/Fabrics.pm
+++ b/src/PVE/Network/SDN/Fabrics.pm
@@ -45,7 +45,7 @@ PVE::JSONSchema::register_standard_option(
{
description => "Type of configuration entry in an SDN Fabric section config",
type => 'string',
- enum => ['openfabric', 'ospf'],
+ enum => ['openfabric', 'ospf', 'isis'],
},
);
@@ -181,6 +181,41 @@ sub node_properties {
description => 'OpenFabric network interface',
optional => 1,
},
+ {
+ type => 'array',
+ 'instance-types' => ['isis'],
+ items => {
+ type => 'string',
+ format => {
+ name => {
+ type => 'string',
+ format => 'pve-iface',
+ description => 'Name of the network interface',
+ },
+ hello_multiplier => {
+ type => 'integer',
+ description => 'The hello_multiplier property of the interface',
+ optional => 1,
+ minimum => 2,
+ maximum => 100,
+ },
+ ip => {
+ type => 'string',
+ format => 'CIDRv4',
+ description => 'IPv4 address for this node',
+ optional => 1,
+ },
+ ip6 => {
+ type => 'string',
+ format => 'CIDRv6',
+ description => 'IPv6 address for this node',
+ optional => 1,
+ },
+ },
+ },
+ description => 'IS-IS network interface',
+ optional => 1,
+ },
{
type => 'array',
'instance-types' => ['ospf'],
@@ -244,8 +279,8 @@ sub fabric_properties {
hello_interval => {
type => 'number',
'type-property' => 'protocol',
- 'instance-types' => ['openfabric'],
- description => 'The hello_interval property for Openfabric',
+ 'instance-types' => ['openfabric', 'isis'],
+ description => 'The hello_interval property for Openfabric and IS-IS',
optional => 1,
minimum => 1,
maximum => 600,
@@ -253,8 +288,8 @@ sub fabric_properties {
csnp_interval => {
type => 'number',
'type-property' => 'protocol',
- 'instance-types' => ['openfabric'],
- description => 'The csnp_interval property for Openfabric',
+ 'instance-types' => ['openfabric', 'isis'],
+ description => 'The csnp_interval property for Openfabric and IS-IS',
optional => 1,
minimum => 1,
maximum => 600,
@@ -277,7 +312,7 @@ sub fabric_properties {
oneOf => [
{
type => 'array',
- 'instance-types' => ['openfabric'],
+ 'instance-types' => ['openfabric', 'isis'],
items => {
type => 'string',
enum => ['hello_interval', 'csnp_interval'],
diff --git a/src/PVE/Network/SDN/Frr.pm b/src/PVE/Network/SDN/Frr.pm
index b548e7b4f0fe..4e933710d967 100644
--- a/src/PVE/Network/SDN/Frr.pm
+++ b/src/PVE/Network/SDN/Frr.pm
@@ -112,6 +112,7 @@ sub restart {
my $SDN_DAEMONS_DEFAULT = {
ospfd => 0,
fabricd => 0,
+ isisd => 0,
};
=head3 set_daemon_status(\%daemons, $set_default)
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH pve-docs 1/1] sdn: add section about IS-IS fabric
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (6 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH pve-network 1/1] fabrics: add IS-IS api types Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
2025-08-19 13:19 ` [pve-devel] [PATCH pve-gui-tests 1/1] fabrics: add screenshots for IS-IS fabric and nodes Gabriel Goller
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Add short section about IS-IS fabrics, nearly the same as the OpenFabric
section. Also add a warning on the IS-IS Controller about the
deprecation and a link to the IS-IS Fabrics. Also mention the
differences between them, which is that the fabrics don't redistribute
connected routes.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
pvesdn.adoc | 111 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 105 insertions(+), 6 deletions(-)
diff --git a/pvesdn.adoc b/pvesdn.adoc
index 87596916d4cb..4cf0355ef9da 100644
--- a/pvesdn.adoc
+++ b/pvesdn.adoc
@@ -509,6 +509,10 @@ bgp-multipath-as-path-relax:: Allow ECMP if your peers have different ASN.
ISIS Controller
~~~~~~~~~~~~~~~
+WARNING: Consider using the xref:pvesdn_isis[IS-IS] Fabric, the ISIS Controller
+will be deprecated soon. Note that the IS-IS Fabric does not redistribute
+connected routes into the fabric.
+
The ISIS controller is not used directly by a zone.
You can use it to configure FRR to export EVPN routes to an ISIS domain.
@@ -603,12 +607,12 @@ behavior and proper source address selection throughout the fabric.
Notes on IPv6
^^^^^^^^^^^^^
-IPv6 is currently only usable on OpenFabric fabrics. These IPv6 Fabrics need
-global IPv6 forwarding enabled on all nodes contained in the fabric. Without
-IPv6 forwarding, non-full-mesh fabrics won't work because the transit nodes
-don't forward packets to the outer nodes. Currently there isn't an easy way to
-enable IPv6 forwarding per-interface like with IPv4, so it has to be enabled
-globally. This can be accomplished by appending this line:
+IPv6 is currently only usable on OpenFabric and IS-IS fabrics. These IPv6
+Fabrics need global IPv6 forwarding enabled on all nodes contained in the
+fabric. Without IPv6 forwarding, non-full-mesh fabrics won't work because the
+transit nodes don't forward packets to the outer nodes. Currently there isn't
+an easy way to enable IPv6 forwarding per-interface like with IPv4, so it has
+to be enabled globally. This can be accomplished by appending this line:
----
post-up sysctl -w net.ipv6.conf.all.forwarding=1
@@ -711,6 +715,101 @@ WARNING: When you remove an interface with an entry in `/etc/network/interfaces`
that has `manual` set, then the IP will not get removed on applying the SDN
configuration.
+
+[[pvesdn_isis]]
+IS-IS
+~~~~~
+
+NOTE: Unlike the IS-IS Controller, the IS-IS Fabric does not distribute all the
+connected routes into the fabric. So it does not set
+`redistribute ipv4/6 connected level-1`.
+
+IS-IS is a routing protocol specifically designed for data center fabrics.
+
+[thumbnail="screenshot/gui-datacenter-create-fabric-isis.png"]
+
+Configuration options:
+
+[[pvesdn_isis_fabric]]
+On the Fabric
+^^^^^^^^^^^^^
+
+Name:: This is the name (domain) of the IS-IS fabric and can be at most 8 characters long.
+
+IPv4 Prefix:: IPv4 CIDR network range (e.g., 192.0.2.0/24) used to verify that
+all router-IDs in the fabric are contained within this prefix.
+
+IPv6 Prefix:: IPv6 CIDR network range (e.g., 2001:db8::/64) used to verify that
+all router-IDs in the fabric are contained within this prefix.
+
+WARNING: For IPv6 fabrics to work, global forwarding needs to be enabled on all
+nodes. Check xref:pvesdn_config_fabrics_ipv6[Notes on IPv6] for how to do it and additional info.
+
+Hello Interval:: Controls how frequently (in seconds) hello packets are sent to
+discover and maintain connections with neighboring nodes. Lower values detect
+failures faster but increase network traffic. This option is global on the
+fabric, meaning every interface on every node in this fabric will inherit this
+hello-interval property. The default value is 3 seconds.
+
+CSNP Interval:: Sets how frequently (in seconds) the node synchronizes its
+routing database with neighbors. Lower values keep the network topology information
+more quickly in sync but increase network traffic. This option is global on the
+fabric, meaning every interface on every node in this fabric will inherit this
+property. The default value is 10 seconds.
+
+[[pvesdn_isis_node]]
+On the Node
+^^^^^^^^^^^
+
+[thumbnail="screenshot/gui-datacenter-create-node-isis.png"]
+
+Options that are available on every node that is part of a fabric:
+
+Node:: Select the node which will be added to the fabric. Only nodes that
+currently are in the cluster will be shown.
+
+IPv4:: A unique IPv4 address used to generate the IS-IS
+Network Entity Title (NET). Each node in the same fabric must have a different
+Router-ID, while a single node must use the same NET address across all fabrics
+(If this is not given {pve} will automatically choose one and ensure that the
+configuration is valid).
+
+IPv6:: A unique IPv6 address used to generate the IS-IS
+Network Entity Title (NET). Each node in the same fabric must have a different
+Router-ID, while a single node must use the same NET address across all fabrics.
+If a IPv4 and IPv6 address is configured, the IPv4 one will be used to derive
+the NET.
+
+WARNING: When using IPv6 addresses, the last 3 segments are used to generate
+the NET. Ensure these segments differ between nodes.
+
+Interfaces:: Specify the interfaces used to establish peering connections with
+other IS-IS nodes. Preferably select interfaces without pre-assigned IP
+addresses, then configure addresses in the IPv4/IPv6 column if needed. A dummy
+"loopback" interface with the router-id is automatically created.
+
+On The Interface
+^^^^^^^^^^^^^^^^
+
+The following optional parameters can be configured per interface when enabling
+the additional columns:
+
+IP::: A IPv4 that should get automatically configured on this interface. Must
+include the netmask (e.g. /31). If no address is set, the interface is
+configured as a `point-to-point` interface.
+
+IPv6::: A IPv6 that should get automatically configured on this interface. Must
+include the netmask (e.g. /127). If no address is set, the interface is
+configured as a `point-to-point` interface.
+
+Hello Multiplier::: Defines how many missed hello packets constitute a failed
+connection. Higher values make the connection more resilient to packet loss but
+slow down failure detection. The default value is 10.
+
+WARNING: When you remove an interface with an entry in `/etc/network/interfaces`
+that has `manual` set, then the IP will not get removed on applying the SDN
+configuration.
+
[[pvesdn_ospf]]
OSPF
~~~~
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread
* [pve-devel] [PATCH pve-gui-tests 1/1] fabrics: add screenshots for IS-IS fabric and nodes
2025-08-19 13:18 [pve-devel] [PATCH docs/gui-tests/manager/network/proxmox{-ve-rs, -perl-rs} 0/9] Add IS-IS protocol to fabrics Gabriel Goller
` (7 preceding siblings ...)
2025-08-19 13:19 ` [pve-devel] [PATCH pve-docs 1/1] sdn: add section about IS-IS fabric Gabriel Goller
@ 2025-08-19 13:19 ` Gabriel Goller
8 siblings, 0 replies; 10+ messages in thread
From: Gabriel Goller @ 2025-08-19 13:19 UTC (permalink / raw)
To: pve-devel
Add IS-IS fabric and node screenshots.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
create_fabrics_screenshots | 43 ++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/create_fabrics_screenshots b/create_fabrics_screenshots
index a83bb222d44b..c0e67f68063c 100755
--- a/create_fabrics_screenshots
+++ b/create_fabrics_screenshots
@@ -14,6 +14,7 @@ sub prepare_fabrics {
my ($conn) = @_;
eval {
# might not exist, so ignore any errors
+ $conn->delete("/cluster/sdn/fabrics/fabric/test3");
$conn->delete("/cluster/sdn/fabrics/fabric/test2");
$conn->delete("/cluster/sdn/fabrics/fabric/test1");
};
@@ -30,14 +31,24 @@ sub prepare_fabrics {
ip_prefix => "198.51.100.0/24",
protocol => "ospf",
};
+ my $isis = {
+ id => "test3",
+ ip_prefix => "192.0.15.0/24",
+ hello_interval => "1",
+ protocol => "isis",
+ };
$conn->post("/cluster/sdn/fabrics/fabric", $openfabric);
$conn->post("/cluster/sdn/fabrics/fabric", $ospf);
+ $conn->post("/cluster/sdn/fabrics/fabric", $isis);
}
sub prepare_nodes {
my ($conn) = @_;
eval {
# might not exist, so ignore any errors
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve0");
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve1");
+ $conn->delete("/cluster/sdn/fabrics/node/test3/pve2");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve0");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve1");
$conn->delete("/cluster/sdn/fabrics/node/test2/pve2");
@@ -53,6 +64,7 @@ sub prepare_nodes {
my $last_octet = $i + 1;
my $ospf_iface = ($i * 2) + 19;
my $openfabric_iface = ($i * 2) + 25;
+ my $isis_iface = ($i * 2) + 30;
my $ospf_node = {
node_id => $node_name,
@@ -72,9 +84,19 @@ sub prepare_nodes {
],
protocol => "openfabric",
};
+ my $isis_node = {
+ node_id => $node_name,
+ ip => "192.0.15.$last_octet",
+ interfaces => [
+ "name=ens$isis_iface",
+ "name=ens" . ($isis_iface + 1)
+ ],
+ protocol => "isis",
+ };
$conn->post("/cluster/sdn/fabrics/node/test1/", $openfabric_node);
$conn->post("/cluster/sdn/fabrics/node/test2/", $ospf_node);
+ $conn->post("/cluster/sdn/fabrics/node/test3/", $isis_node);
}
}
@@ -131,6 +153,18 @@ sub create_fabrics_ui_screenshots {
$gui->element_screenshot("gui-datacenter-create-fabric-openfabric.png", $window);
$gui->window_close($window);
+ $menu = $gui->find_button('Add Fabric', $panel)->click();
+ $gui->find_menu_item('IS-IS')->click();
+
+ $window = $gui->find_dialog("Create: IS-IS");
+ $gui->setValue($window, 'id', 'test3');
+ $gui->setValue($window, 'ip_prefix', '192.0.2.0/24');
+ $gui->setValue($window, 'hello_interval', '1');
+
+ $gui->element_screenshot("gui-datacenter-create-fabric-isis.png", $window);
+ $gui->window_close($window);
+
+
$menu = $gui->find_button('Add Fabric', $panel)->click();
$gui->find_menu_item('OSPF')->click();
@@ -164,6 +198,15 @@ sub create_fabrics_ui_screenshots {
$gui->element_screenshot("gui-datacenter-create-node-ospf.png", $window);
$gui->window_close($window);
+ open_add_node_panel($gui, "isis");
+ sleep_ms(500);
+
+ $window = $gui->find_dialog("Create: Node");
+ $gui->setValue($window, 'ip', '198.51.100.1');
+ select_interfaces($gui, "ens19", "ens20");
+ $gui->element_screenshot("gui-datacenter-create-node-isis.png", $window);
+ $gui->window_close($window);
+
# get fabric overview
prepare_nodes($conn);
sleep_ms(250);
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 10+ messages in thread