diff --git a/Cargo.lock b/Cargo.lock index 9645aa407..aac802381 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1253,6 +1253,7 @@ dependencies = [ "bolero", "caps", "chrono", + "dataplane-gwname", "dataplane-hardware", "dataplane-k8s-intf", "dataplane-lpm", @@ -1460,6 +1461,7 @@ dependencies = [ "dataplane-args", "dataplane-concurrency", "dataplane-config", + "dataplane-gwname", "dataplane-id", "dataplane-interface-manager", "dataplane-k8s-intf", @@ -1508,8 +1510,10 @@ dependencies = [ "dataplane-net", "dataplane-pipeline", "dataplane-pkt-meta", + "dataplane-test-utils", "dataplane-tracectl", "etherparse", + "fixin", "left-right", "linkme", "rand 0.9.2", @@ -2142,9 +2146,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d91fd049c123429b018c47887d3f75a265540dd3c30ba9cb7bae9197edb03a" +checksum = "824f08d01d0f496b3eca4f001a13cf17690a6ee930043d20817f547455fd98f8" dependencies = [ "autocfg", "tokio", @@ -2277,8 +2281,8 @@ dependencies = [ [[package]] name = "gateway_config" -version = "0.17.0" -source = "git+https://github.com/githedgehog/gateway-proto?tag=v0.17.0#aeae316decdac1db7d990ff50be36c5a5b00ebd0" +version = "0.20.0" +source = "git+https://github.com/githedgehog/gateway-proto?tag=v0.20.0#387d60479182a3c90ad8f06c6af08995092d20e4" dependencies = [ "async-trait", "bolero", @@ -4437,9 +4441,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64 0.22.1", "bytes", @@ -4521,9 +4525,9 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "roaring" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08d6a905edb32d74a5d5737a0c9d7e950c312f3c46cb0ca0a2ca09ea11878a0" +checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" [[package]] name = "rtnetlink" diff --git a/Cargo.toml b/Cargo.toml index df7f24b85..5453f089f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ dpdk-sysroot-helper = { path = "./dpdk-sysroot-helper", package = "dataplane-dpd dplane-rpc = { git = "https://github.com/githedgehog/dplane-rpc.git", rev = "e8fc33db10e1d00785f2a2b90cbadcad7900f200", features = [] } errno = { path = "./errno", package = "dataplane-errno", features = [] } flow-info = { path = "./flow-info", package = "dataplane-flow-info", features = [] } -gateway_config = { git = "https://github.com/githedgehog/gateway-proto", tag = "v0.17.0", features = [] } +gateway_config = { git = "https://github.com/githedgehog/gateway-proto", tag = "v0.20.0", features = [] } gwname = { path = "./gwname", package = "dataplane-gwname", features = [] } hardware = { path = "./hardware", package = "dataplane-hardware", features = [] } id = { path = "./id", package = "dataplane-id", features = [] } diff --git a/config/Cargo.toml b/config/Cargo.toml index 72fa23824..9a3884c08 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] # internal gateway_config = { workspace = true } +gwname = { workspace = true } hardware = { workspace = true } net = { workspace = true } lpm = { workspace = true } diff --git a/config/src/converters/grpc/device.rs b/config/src/converters/grpc/device.rs index 3bc17a21a..0854ea529 100644 --- a/config/src/converters/grpc/device.rs +++ b/config/src/converters/grpc/device.rs @@ -4,33 +4,14 @@ use ::gateway_config::config as gateway_config; use gateway_config::TracingConfig as ApiTracingConfig; -use crate::internal::device::{ - DeviceConfig, - settings::{DeviceSettings, DpdkPortConfig, KernelPacketConfig, PacketDriver}, - tracecfg::TracingConfig, -}; +use crate::internal::device::{DeviceConfig, tracecfg::TracingConfig}; impl TryFrom<&gateway_config::Device> for DeviceConfig { type Error = String; fn try_from(device: &gateway_config::Device) -> Result { - // Convert driver enum - let driver = match ::gateway_config::PacketDriver::try_from(device.driver) { - Ok(::gateway_config::PacketDriver::Kernel) => { - PacketDriver::Kernel(KernelPacketConfig {}) - } - Ok(::gateway_config::PacketDriver::Dpdk) => PacketDriver::DPDK(DpdkPortConfig {}), - Err(_) => return Err(format!("Invalid driver value: {}", device.driver)), - }; - - // Create device settings - let mut device_settings = DeviceSettings::new(&device.hostname); - device_settings = device_settings.set_packet_driver(driver); - - // Create DeviceConfig with these settings - // Note: PortConfig is not yet implemented, so we don't add any ports - let mut device_config = DeviceConfig::new(device_settings); - + // Create DeviceConfig + let mut device_config = DeviceConfig::new(); if let Some(tracing) = &device.tracing { device_config.set_tracing(TracingConfig::try_from(tracing)?); } @@ -42,21 +23,8 @@ impl TryFrom<&DeviceConfig> for gateway_config::Device { type Error = String; fn try_from(device: &DeviceConfig) -> Result { - let driver = match device.settings.driver { - PacketDriver::Kernel(_) => ::gateway_config::PacketDriver::Kernel, - PacketDriver::DPDK(_) => ::gateway_config::PacketDriver::Dpdk, - }; - - // Convert ports if available - let ports = Vec::new(); // TODO: Implement port conversion when needed let tracing = device.tracing.as_ref().map(ApiTracingConfig::from); - Ok(gateway_config::Device { - driver: driver.into(), - hostname: device.settings.hostname.clone(), - eal: None, // TODO: Handle EAL configuration when needed - ports, - tracing, - }) + Ok(gateway_config::Device { tracing }) } } diff --git a/config/src/converters/grpc/gateway_config.rs b/config/src/converters/grpc/gateway_config.rs index 88aa105d2..69b542ed1 100644 --- a/config/src/converters/grpc/gateway_config.rs +++ b/config/src/converters/grpc/gateway_config.rs @@ -4,10 +4,13 @@ use tracing::warn; use crate::external::ExternalConfigBuilder; +use crate::external::communities::PriorityCommunityTable; +use crate::external::gwgroup::{GwGroup, GwGroupTable}; use crate::external::overlay::Overlay; use crate::external::underlay::Underlay; -use crate::internal::device::{DeviceConfig, settings::DeviceSettings}; +use crate::internal::device::DeviceConfig; use crate::{ExternalConfig, GwConfig}; +use gateway_config::config::GatewayGroup; // Helper Functions //-------------------------------------------------------------------------------- @@ -26,7 +29,7 @@ pub fn convert_gateway_config_from_grpc_with_defaults( DeviceConfig::try_from(device)? } else { warn!("Missing device configuration!"); - DeviceConfig::new(DeviceSettings::new("Unset")) + DeviceConfig::new() }; // convert underlay or provide a default (empty) @@ -45,12 +48,29 @@ pub fn convert_gateway_config_from_grpc_with_defaults( Overlay::default() }; + // convert gateway groups + let mut gw_groups = GwGroupTable::new(); + for g in &grpc_config.gw_groups { + let group = GwGroup::try_from(g)?; + gw_groups.add_group(group).map_err(|e| e.to_string())?; + } + + // convert community table + let mut comtable = PriorityCommunityTable::new(); + for (prio, community) in &grpc_config.communities { + comtable + .insert(*prio, community) + .map_err(|e| e.to_string())?; + } + // Create the ExternalConfig using the builder pattern let external_config = ExternalConfigBuilder::default() .genid(grpc_config.generation) .device(device_config) .underlay(underlay_config) .overlay(overlay_config) + .gwgroups(gw_groups) + .communities(comtable) .build() .map_err(|e| format!("Failed to build ExternalConfig: {e}"))?; @@ -82,12 +102,29 @@ impl TryFrom<&gateway_config::GatewayConfig> for ExternalConfig { Err("Missing overlay configuration!".to_string()) }?; + // convert gateway groups + let mut gw_groups = GwGroupTable::new(); + for g in &grpc_config.gw_groups { + let group = GwGroup::try_from(g)?; + gw_groups.add_group(group).map_err(|e| e.to_string())?; + } + + // convert community table + let mut comtable = PriorityCommunityTable::new(); + for (prio, community) in &grpc_config.communities { + comtable + .insert(*prio, community) + .map_err(|e| e.to_string())?; + } + // Create the ExternalConfig using the builder pattern let external_config = ExternalConfigBuilder::default() .genid(grpc_config.generation) .device(device_config) .underlay(underlay_config) .overlay(overlay_config) + .gwgroups(gw_groups) + .communities(comtable) .build() .map_err(|e| format!("Failed to build ExternalConfig: {e}"))?; @@ -108,12 +145,21 @@ impl TryFrom<&ExternalConfig> for gateway_config::GatewayConfig { // Convert overlay config let overlay = gateway_config::Overlay::try_from(&external_config.overlay)?; + // Convert gateway groups + let gw_groups: Vec<_> = external_config + .gwgroups + .iter() + .map(|g| GatewayGroup::try_from(g).unwrap_or_else(|_| unreachable!())) + .collect(); + // Create the complete gRPC config Ok(gateway_config::GatewayConfig { generation: external_config.genid, device: Some(device), underlay: Some(underlay), overlay: Some(overlay), + gw_groups, + communities: external_config.communities.inner().clone(), }) } } diff --git a/config/src/converters/grpc/gwgroups.rs b/config/src/converters/grpc/gwgroups.rs new file mode 100644 index 000000000..b8edaaf7a --- /dev/null +++ b/config/src/converters/grpc/gwgroups.rs @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +use crate::converters::strings::parse_address; +use crate::external::gwgroup::{GwGroup, GwGroupMember}; +use gateway_config::config as gateway_config; + +impl TryFrom<&gateway_config::GatewayGroupMember> for GwGroupMember { + type Error = String; + + fn try_from(value: &gateway_config::GatewayGroupMember) -> Result { + let address = parse_address(&value.ipaddress) + .map_err(|e| format!("Bad ip address '{}': {e}", value.ipaddress))?; + Ok(GwGroupMember::new(&value.name, value.priority, address)) + } +} +impl TryFrom<&GwGroupMember> for gateway_config::GatewayGroupMember { + type Error = String; + + fn try_from(value: &GwGroupMember) -> Result { + Ok(gateway_config::GatewayGroupMember { + name: value.name.clone(), + priority: value.priority, + ipaddress: value.ipaddress.to_string(), + }) + } +} + +impl TryFrom<&gateway_config::GatewayGroup> for GwGroup { + type Error = String; + + fn try_from(value: &gateway_config::GatewayGroup) -> Result { + let mut rgroup = GwGroup::new(&value.name); + for m in &value.members { + let member = GwGroupMember::try_from(m)?; + rgroup.add_member(member).map_err(|e| e.to_string())?; + } + Ok(rgroup) + } +} + +impl TryFrom<&GwGroup> for gateway_config::GatewayGroup { + type Error = String; + + fn try_from(value: &GwGroup) -> Result { + let members: Vec<_> = value + .iter() + .map(|m| { + gateway_config::GatewayGroupMember::try_from(m).unwrap_or_else(|_| unreachable!()) + }) + .collect(); + Ok(Self { + name: value.name().to_owned(), + members, + }) + } +} diff --git a/config/src/converters/grpc/mod.rs b/config/src/converters/grpc/mod.rs index a4a8b0360..5ab37a131 100644 --- a/config/src/converters/grpc/mod.rs +++ b/config/src/converters/grpc/mod.rs @@ -9,6 +9,7 @@ mod bgp; mod device; mod expose; mod gateway_config; +mod gwgroups; mod interface; mod overlay; mod peering; @@ -25,6 +26,8 @@ pub use device::*; pub use expose::*; pub use gateway_config::*; #[allow(unused)] // Remove if we do anything but implement traits +pub use gwgroups::*; +#[allow(unused)] // Remove if we do anything but implement traits pub use interface::*; #[allow(unused)] // Remove if we do anything but implement traits pub use overlay::*; @@ -44,6 +47,8 @@ pub use vrf::*; #[cfg(test)] mod test { use gateway_config::GatewayConfig; + use gateway_config::GatewayGroupMember; + use gateway_config::config::GatewayGroup; use gateway_config::config::TracingConfig as ApiTracingConfig; use pretty_assertions::assert_eq; @@ -51,6 +56,7 @@ mod test { use crate::converters::grpc::{ convert_dataplane_status_from_grpc, convert_dataplane_status_to_grpc, }; + use crate::external::communities::PriorityCommunityTable; use crate::internal::device::DeviceConfig; use crate::internal::interfaces::interface::InterfaceConfig; @@ -129,10 +135,6 @@ mod test { fn create_test_gateway_config() -> GatewayConfig { // Create device let device = gateway_config::Device { - driver: 0, // Kernel - hostname: "test-gateway".to_string(), - ports: Vec::new(), - eal: None, tracing: Some(create_tracing_config()), }; @@ -305,6 +307,7 @@ mod test { let peering = gateway_config::VpcPeering { name: "vpc1-vpc2-peering".to_string(), r#for: vec![vpc1_entry, vpc2_entry], + gateway_group: "gw-group-1".to_string(), }; // Create Overlay @@ -313,12 +316,39 @@ mod test { peerings: vec![peering], }; + // Create gateway group + let gw_group = GatewayGroup { + name: "gw-group-1".to_string(), + members: vec![ + GatewayGroupMember { + name: "gw1".to_owned(), + priority: 1, + ipaddress: "172.128.0.1".to_string(), + }, + GatewayGroupMember { + name: "gw2".to_owned(), + priority: 2, + ipaddress: "172.128.0.2".to_string(), + }, + ], + }; + + // Create priority-to-community table + let mut commtable = PriorityCommunityTable::new(); + commtable.insert(0, "65000:800").unwrap(); + commtable.insert(1, "65000:801").unwrap(); + commtable.insert(2, "65000:802").unwrap(); + commtable.insert(3, "65000:803").unwrap(); + commtable.insert(4, "65000:804").unwrap(); + // Create the full GatewayConfig GatewayConfig { generation: 42, device: Some(device), underlay: Some(underlay), overlay: Some(overlay), + gw_groups: vec![gw_group], + communities: commtable.inner().clone(), } } @@ -356,10 +386,6 @@ mod test { // Create test data with specific components let device = gateway_config::Device { - driver: 0, // Kernel - hostname: "test-device".to_string(), - ports: Vec::new(), - eal: None, tracing: Some(tracing.clone()), }; @@ -383,14 +409,11 @@ mod test { "TryFrom for DeviceConfig failed" ); let device_config = device_config_result.unwrap(); - assert_eq!(device_config.settings.hostname, "test-device"); // Back to gRPC let device_back_result = gateway_config::Device::try_from(&device_config); assert!(device_back_result.is_ok(), "TryFrom back to Device failed"); let device_back = device_back_result.unwrap(); - assert_eq!(device_back.hostname, device.hostname); - assert_eq!(device_back.driver, device.driver); assert_eq!(device_back.tracing.as_ref().unwrap(), &tracing); // InterfaceConfig TryFrom diff --git a/config/src/converters/grpc/peering.rs b/config/src/converters/grpc/peering.rs index 4a6cbd848..4937f19df 100644 --- a/config/src/converters/grpc/peering.rs +++ b/config/src/converters/grpc/peering.rs @@ -24,8 +24,19 @@ impl TryFrom<&gateway_config::VpcPeering> for VpcPeering { )), }?; + let gwgroup = if peering.gateway_group.is_empty() { + None + } else { + Some(peering.gateway_group.clone()) + }; + // Create the peering using the constructor - Ok(VpcPeering::new(&peering.name, vpc1_manifest, vpc2_manifest)) + Ok(VpcPeering::new( + &peering.name, + vpc1_manifest, + vpc2_manifest, + gwgroup, + )) } } @@ -40,6 +51,7 @@ impl TryFrom<&VpcPeering> for gateway_config::VpcPeering { Ok(gateway_config::VpcPeering { name: peering.name.clone(), r#for: vec![left_for, right_for], + gateway_group: peering.gw_group.clone().unwrap_or_default(), }) } } diff --git a/config/src/converters/k8s/config/communities.rs b/config/src/converters/k8s/config/communities.rs new file mode 100644 index 000000000..5988c7b72 --- /dev/null +++ b/config/src/converters/k8s/config/communities.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +use crate::converters::k8s::FromK8sConversionError; +use k8s_intf::gateway_agent_crd::GatewayAgentSpec; + +use crate::external::communities::PriorityCommunityTable; + +impl TryFrom<&GatewayAgentSpec> for PriorityCommunityTable { + type Error = FromK8sConversionError; + + fn try_from(spec: &GatewayAgentSpec) -> Result { + let mut comtable = PriorityCommunityTable::new(); + match &spec.communities { + None => Ok(comtable), + Some(map) => { + for (prio, community) in map { + let priority: u32 = prio.parse().map_err(|e| { + Self::Error::ParseError(format!("Community priority '{prio}': {e}")) + })?; + comtable.insert(priority, community)?; + } + Ok(comtable) + } + } + } +} diff --git a/config/src/converters/k8s/config/device.rs b/config/src/converters/k8s/config/device.rs index 992d9b848..6b9820248 100644 --- a/config/src/converters/k8s/config/device.rs +++ b/config/src/converters/k8s/config/device.rs @@ -5,25 +5,13 @@ use k8s_intf::gateway_agent_crd::GatewayAgent; use crate::converters::k8s::FromK8sConversionError; use crate::internal::device::DeviceConfig; -use crate::internal::device::settings::{DeviceSettings, KernelPacketConfig, PacketDriver}; use crate::internal::device::tracecfg::TracingConfig; impl TryFrom<&GatewayAgent> for DeviceConfig { type Error = FromK8sConversionError; fn try_from(ga: &GatewayAgent) -> Result { - // We don't really use this, we take the actual value from the CLI - let driver = PacketDriver::Kernel(KernelPacketConfig {}); - - // Create device settings - let mut device_settings = DeviceSettings::new(ga.metadata.name.as_ref().ok_or( - FromK8sConversionError::MissingData("metadata.name is required".to_string()), - )?); - device_settings = device_settings.set_packet_driver(driver); - - // Create DeviceConfig with these settings - // Note: PortConfig is not yet implemented, so we don't add any ports - let mut device_config = DeviceConfig::new(device_settings); + let mut device_config = DeviceConfig::new(); if let Some(logs) = &ga .spec @@ -53,11 +41,6 @@ mod test { .for_each(|ga| { let ga = ga.as_ref(); let dev = DeviceConfig::try_from(ga).unwrap(); - assert_eq!(&dev.settings.hostname, ga.metadata.name.as_ref().unwrap()); - assert!(matches!( - &dev.settings.driver, - &PacketDriver::Kernel(KernelPacketConfig {}) - )); // Make sure we set tracing, the conversion is tested as part of the `TraceConfig` conversion assert_eq!( ga.spec.gateway.as_ref().unwrap().logs.is_some(), diff --git a/config/src/converters/k8s/config/gateway_config.rs b/config/src/converters/k8s/config/gateway_config.rs index 7af27a5a7..431e1d20d 100644 --- a/config/src/converters/k8s/config/gateway_config.rs +++ b/config/src/converters/k8s/config/gateway_config.rs @@ -5,6 +5,8 @@ use k8s_intf::gateway_agent_crd::GatewayAgent; use crate::DeviceConfig; use crate::converters::k8s::FromK8sConversionError; +use crate::external::communities::PriorityCommunityTable; +use crate::external::gwgroup::GwGroupTable; use crate::external::overlay::Overlay; use crate::external::underlay::Underlay; use crate::external::{ExternalConfig, ExternalConfigBuilder}; @@ -22,6 +24,13 @@ impl TryFrom<&GatewayAgent> for ExternalConfig { "metadata.name not found".to_string(), ))? .as_str(); + + let Some(gen_id) = ga.metadata.generation else { + return Err(FromK8sConversionError::Invalid(format!( + "metadata.generation not found for {name}" + ))); + }; + let device = DeviceConfig::try_from(ga)?; let underlay = Underlay::try_from(ga.spec.gateway.as_ref().ok_or( FromK8sConversionError::MissingData(format!( @@ -29,16 +38,16 @@ impl TryFrom<&GatewayAgent> for ExternalConfig { )), )?)?; let overlay = Overlay::try_from(&ga.spec)?; - let Some(gen_id) = ga.metadata.generation else { - return Err(FromK8sConversionError::Invalid(format!( - "metadata.generation not found for {name}" - ))); - }; + let gwgroup_table = GwGroupTable::try_from(&ga.spec)?; + let comtable = PriorityCommunityTable::try_from(&ga.spec)?; + let external_config = ExternalConfigBuilder::default() .genid(gen_id) .device(device) .underlay(underlay) .overlay(overlay) + .gwgroups(gwgroup_table) + .communities(comtable) .build() .map_err(|e| { FromK8sConversionError::InternalError(format!( diff --git a/config/src/converters/k8s/config/gwgroups.rs b/config/src/converters/k8s/config/gwgroups.rs new file mode 100644 index 000000000..7ccd18077 --- /dev/null +++ b/config/src/converters/k8s/config/gwgroups.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +use crate::converters::k8s::FromK8sConversionError; +use crate::converters::strings::parse_address; +use k8s_intf::gateway_agent_crd::{GatewayAgentGroupsMembers, GatewayAgentSpec}; + +use crate::external::gwgroup::{GwGroup, GwGroupMember, GwGroupTable}; + +impl TryFrom<&GatewayAgentGroupsMembers> for GwGroupMember { + type Error = FromK8sConversionError; + + fn try_from(value: &GatewayAgentGroupsMembers) -> Result { + let name = value + .name + .as_ref() + .ok_or_else(|| Self::Error::MissingData("Gateway group member name".to_string()))?; + + let priority: u32 = value.priority.unwrap_or(0); + //.ok_or_else(|| Self::Error::MissingData("Gateway group member priority".to_string()))?; + + let address = value.vtep_ip.as_ref().ok_or_else(|| { + Self::Error::MissingData("Gateway group member ip address".to_string()) + })?; + let ipaddress = parse_address(address) + .map_err(|e| Self::Error::ParseError(format!("Invalid ip address {address}: {e}")))?; + + Ok(Self { + name: name.clone(), + priority, + ipaddress, + }) + } +} + +impl TryFrom<&GatewayAgentSpec> for GwGroupTable { + type Error = FromK8sConversionError; + + fn try_from(spec: &GatewayAgentSpec) -> Result { + let mut group_table = GwGroupTable::new(); + + match &spec.groups { + None => Ok(group_table), + Some(map) => { + for (name, gagroups) in map { + let mut group = GwGroup::new(name); + let members = gagroups.members.as_ref().ok_or_else(|| { + Self::Error::MissingData(format!("Gateway group members for group {name}")) + })?; + for m in members { + let member = GwGroupMember::try_from(m)?; + group.add_member(member)?; + } + group_table.add_group(group)?; + } + Ok(group_table) + } + } + } +} diff --git a/config/src/converters/k8s/config/mod.rs b/config/src/converters/k8s/config/mod.rs index 908285c7e..55260eae1 100644 --- a/config/src/converters/k8s/config/mod.rs +++ b/config/src/converters/k8s/config/mod.rs @@ -6,9 +6,11 @@ #![deny(clippy::all, clippy::pedantic)] pub mod bgp; +pub mod communities; pub mod device; pub mod expose; pub mod gateway_config; +pub mod gwgroups; pub mod interface; pub mod overlay; pub mod peering; diff --git a/config/src/converters/k8s/config/peering.rs b/config/src/converters/k8s/config/peering.rs index 100a30100..b510c4054 100644 --- a/config/src/converters/k8s/config/peering.rs +++ b/config/src/converters/k8s/config/peering.rs @@ -31,6 +31,7 @@ impl TryFrom<(&VpcSubnetMap, &str, &GatewayAgentPeerings)> for VpcPeering { fn try_from( (vpc_subnets, peering_name, peering): (&VpcSubnetMap, &str, &GatewayAgentPeerings), ) -> Result { + let gwgroup = peering.gateway_group.clone(); // we don't fail atm if not set if let Some(peering) = peering.peering.as_ref() { let num_peerings = peering.len(); if peering.len() != 2 { @@ -51,7 +52,7 @@ impl TryFrom<(&VpcSubnetMap, &str, &GatewayAgentPeerings)> for VpcPeering { let right = manifests.pop().unwrap_or_else(|| unreachable!()); let left = manifests.pop().unwrap_or_else(|| unreachable!()); - Ok(VpcPeering::new(peering_name, left, right)) + Ok(VpcPeering::new(peering_name, left, right, gwgroup)) } else { Err(FromK8sConversionError::Invalid( "Missing peering".to_string(), diff --git a/config/src/converters/k8s/config/underlay.rs b/config/src/converters/k8s/config/underlay.rs index 23d610fe0..84fc8e667 100644 --- a/config/src/converters/k8s/config/underlay.rs +++ b/config/src/converters/k8s/config/underlay.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Open Network Fabric Authors +use crate::converters::strings::parse_address_v4; use std::net::IpAddr; -use ipnet::Ipv4Net; use k8s_intf::gateway_agent_crd::GatewayAgentGateway; use lpm::prefix::{Prefix, PrefixString}; use net::eth::mac::SourceMac; @@ -98,14 +98,9 @@ fn add_bgp_config( "Gateway protocol IP not specified".to_string(), ))?; - let router_id = protocol_ip - .parse::() - .map_err(|e| { - FromK8sConversionError::ParseError(format!( - "Invalid IPv4 protocol IP {protocol_ip}: {e}" - )) - })? - .addr(); + let router_id = parse_address_v4(protocol_ip).map_err(|e| { + FromK8sConversionError::ParseError(format!("Invalid IPv4 protocol IP {protocol_ip}: {e}")) + })?; let vtep_ip_raw = gateway .vtep_ip @@ -113,6 +108,7 @@ fn add_bgp_config( .ok_or(FromK8sConversionError::MissingData( "Gateway VTEP IP not specified".to_string(), ))?; + let vtep_prefix = Prefix::try_from(PrefixString(vtep_ip_raw)).map_err(|e| { FromK8sConversionError::ParseError(format!("Invalid VTEP IP {vtep_ip_raw}: {e}")) })?; diff --git a/config/src/converters/k8s/mod.rs b/config/src/converters/k8s/mod.rs index f04491174..6734e991e 100644 --- a/config/src/converters/k8s/mod.rs +++ b/config/src/converters/k8s/mod.rs @@ -6,6 +6,7 @@ pub mod config; pub mod status; +use crate::ConfigError; use thiserror::Error; #[derive(Debug, Error)] @@ -18,6 +19,8 @@ pub enum FromK8sConversionError { ParseError(String), #[error("Internal CRD object conversion error: {0}")] InternalError(String), + #[error("Configuration error: {0}")] + ConfigError(#[from] ConfigError), } #[derive(Debug, Error)] diff --git a/config/src/converters/mod.rs b/config/src/converters/mod.rs index 2be7132b0..0b7db5bf2 100644 --- a/config/src/converters/mod.rs +++ b/config/src/converters/mod.rs @@ -5,3 +5,4 @@ pub mod grpc; pub mod k8s; +pub mod strings; diff --git a/config/src/converters/strings.rs b/config/src/converters/strings.rs new file mode 100644 index 000000000..d2ba4a3c3 --- /dev/null +++ b/config/src/converters/strings.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +//! Commonly used conversions from strings + +use ipnet::{AddrParseError, IpNet, Ipv4Net}; +use std::net::{IpAddr, Ipv4Addr}; + +/// Parse a string containing an IP address. If the string contains a mask +/// length, ignore it. On Success, returns an `IpAddr`. +/// +/// # Errors +/// This function returns `AddrParseError` if the address could not be parsed. +pub fn parse_address(input: &str) -> Result { + match input.parse::() { + Ok(address) => Ok(address), + Err(_) => match input.parse::()? { + IpNet::V4(a) => Ok(a.addr().into()), + IpNet::V6(a) => Ok(a.addr().into()), + }, + } +} + +/// Parse a string containing an IPv4 address. If the string contains a mask +/// length, ignore it. On Success, returns an `Ipv4Addr`. +/// +/// # Errors +/// This function returns `AddrParseError` if the address could not be parsed. +pub fn parse_address_v4(input: &str) -> Result { + match input.parse::() { + Ok(address) => Ok(address), + Err(_) => Ok(input.parse::()?.addr()), + } +} diff --git a/config/src/display.rs b/config/src/display.rs index d7da0aed1..1d2750dfd 100644 --- a/config/src/display.rs +++ b/config/src/display.rs @@ -83,10 +83,28 @@ fn fmt_remote_manifest( } Ok(()) } +fn fmt_peering_communities(f: &mut std::fmt::Formatter<'_>, peering: &Peering) -> std::fmt::Result { + write!(f, " communities:")?; + if peering.adv_communities.is_empty() { + writeln!(f, "none")?; + } else { + for comm in &peering.adv_communities { + write!(f, " {comm}")?; + } + writeln!(f)?; + } + Ok(()) +} impl Display for Peering { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, " ■ {}:", self.name)?; + write!( + f, + " gwgroup: {}", + self.gwgroup.as_ref().map_or("none", |v| v) + )?; + fmt_peering_communities(f, self)?; fmt_local_manifest(f, &self.local)?; writeln!(f)?; fmt_remote_manifest(f, &self.remote, &self.remote_id)?; diff --git a/config/src/errors.rs b/config/src/errors.rs index 5d78db954..9efe3d866 100644 --- a/config/src/errors.rs +++ b/config/src/errors.rs @@ -28,8 +28,14 @@ pub enum ConfigError { DuplicateVpcPeeringId(String), #[error("Peering '{0}' refers to a VPC for which a peering already exists")] DuplicateVpcPeerings(String), + #[error("Duplicated gateway group '{0}'")] + DuplicateGroup(String), + #[error("Duplicated gateway group member '{0}'")] + DuplicateMember(String), #[error("A VPC peering object refers to non-existent VPC '{0}'")] NoSuchVpc(String), + #[error("A VPC peering object refers to non-existent group '{0}'")] + NoSuchGroup(String), #[error("'{0}' is not a valid VNI")] InvalidVpcVni(u32), #[error("Config with id {0} not found")] @@ -83,6 +89,12 @@ pub enum ConfigError { // tracing #[error("Failed to set tracing configuration: {0}")] Tracing(#[from] tracectl::TraceCtlError), + + // Community mappings + #[error("Priority {0} has no community mapping")] + UnmappedPriority(u32), + #[error("Communiity {0} is mapped from distinct priorities")] + DuplicateCommunity(String), } /// Result-like type for configurations diff --git a/config/src/external/communities.rs b/config/src/external/communities.rs new file mode 100644 index 000000000..6f98ae9e6 --- /dev/null +++ b/config/src/external/communities.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +//! Dataplane configuration model: priority to community table + +use std::collections::HashMap; +use std::fmt::Display; + +use crate::ConfigError; + +#[derive(Clone, Debug, Default)] +pub struct PriorityCommunityTable(HashMap); +impl PriorityCommunityTable { + #[must_use] + pub fn new() -> Self { + Self::default() + } + #[must_use] + /// Get a reference to the inner map + pub fn inner(&self) -> &HashMap { + &self.0 + } + /// Insert a priority-to-community mapping + pub fn insert(&mut self, prio: u32, community: &str) -> Result<(), ConfigError> { + if self.0.iter().any(|(_, comm)| comm == community) { + return Err(ConfigError::DuplicateCommunity(community.to_string())); + } + self.0.insert(prio, community.to_owned()); + Ok(()) + } + /// Get the community for a given priority + pub fn get_community(&self, prio: u32) -> Result<&String, ConfigError> { + self.0.get(&prio).ok_or(ConfigError::UnmappedPriority(prio)) + } +} + +macro_rules! COMMUNITY_MAPPING_FMT { + ($prio:expr, $community:expr) => { + format_args!(" {:>6} {:<16}", $prio, $community) + }; +} + +impl Display for PriorityCommunityTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, " ━━━━━━━ Community mappings ━━━━━━━")?; + writeln!(f, "{}", COMMUNITY_MAPPING_FMT!("prio", "community"))?; + for (prio, comm) in self.inner() { + writeln!(f, "{}", COMMUNITY_MAPPING_FMT!(prio, comm))?; + } + Ok(()) + } +} diff --git a/config/src/external/gwgroup.rs b/config/src/external/gwgroup.rs new file mode 100644 index 000000000..e7794ee9d --- /dev/null +++ b/config/src/external/gwgroup.rs @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +//! Dataplane configuration model: gateway groups + +use crate::ConfigError; +use std::collections::HashMap; +use std::fmt::Display; +use std::net::IpAddr; + +/// A [`GwGroupMember`] represents a gateway within a [`GwGroup`]. +/// Gateways are uniquely identified by their name. Within a group, each +/// gateway has a priority. +#[derive(Clone, Debug)] +pub struct GwGroupMember { + pub name: String, + pub priority: u32, + pub ipaddress: IpAddr, +} +impl GwGroupMember { + #[must_use] + pub fn new(name: &str, priority: u32, address: IpAddr) -> Self { + Self { + name: name.to_owned(), + priority, + ipaddress: address, + } + } +} + +/// A [`GwGroup`] is a named set of gateways. Each gateway is represented by a [`GwGroupMember`]. +#[derive(Clone, Debug)] +pub struct GwGroup { + name: String, + members: Vec, +} +impl GwGroup { + #[must_use] + pub fn new(name: &str) -> Self { + Self { + name: name.to_owned(), + members: vec![], + } + } + #[must_use] + pub fn sorted(&self) -> GwGroup { + let mut clone = self.clone(); + clone + .members + .sort_by(|m1, m2| m2.priority.cmp(&m1.priority)); + clone + } + pub fn sort_members(&mut self) { + self.members.sort_by(|m1, m2| m2.priority.cmp(&m1.priority)); + } + pub fn add_member(&mut self, member: GwGroupMember) -> Result<(), ConfigError> { + if self.get_member_by_name(&member.name).is_some() { + return Err(ConfigError::DuplicateMember(member.name.clone())); + } + if self.get_member_by_addr(member.ipaddress).is_some() { + return Err(ConfigError::DuplicateMember(member.ipaddress.to_string())); + } + self.members.push(member); + Ok(()) + } + pub fn iter(&self) -> impl Iterator { + self.members.iter() + } + #[must_use] + pub fn name(&self) -> &str { + self.name.as_str() + } + #[must_use] + pub fn get_member_by_name(&self, name: &str) -> Option<&GwGroupMember> { + self.members + .iter() + .find(|&m| m.name == name) + .map(|v| v as _) + } + #[must_use] + pub fn get_member_by_addr(&self, ipaddress: IpAddr) -> Option<&GwGroupMember> { + self.members + .iter() + .find(|&m| m.ipaddress == ipaddress) + .map(|v| v as _) + } + #[must_use] + pub fn get_member_pos(&self, name: &str) -> Option { + self.members.iter().position(|m| m.name.as_str() == name) + } +} + +#[derive(Clone, Debug, Default)] +pub struct GwGroupTable(HashMap); +impl GwGroupTable { + #[must_use] + pub fn new() -> Self { + Self::default() + } + pub fn add_group(&mut self, group: GwGroup) -> Result<(), ConfigError> { + if self.0.contains_key(group.name()) { + return Err(ConfigError::DuplicateGroup(group.name().to_owned())); + } + self.0.insert(group.name().to_owned(), group); + Ok(()) + } + #[must_use] + pub fn get_group(&self, name: &str) -> Option<&GwGroup> { + self.0.get(name) + } + pub fn iter(&self) -> impl Iterator { + self.0.values() + } + #[must_use] + pub fn get_group_member(&self, group: &str, name: &str) -> Option<&GwGroupMember> { + self.get_group(group) + .map(|group| group.get_member_by_name(name))? + } +} + +macro_rules! GW_GROUP_MEMBER_FMT { + ($name:expr, $prio:expr, $address:expr) => { + format_args!(" {:<16} {:<5} {:<40}", $name, $prio, $address) + }; +} + +impl Display for GwGroupMember { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + GW_GROUP_MEMBER_FMT!(self.name, self.priority, self.ipaddress) + ) + } +} +impl Display for GwGroup { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, " {}:", self.name())?; + writeln!(f, "{}", GW_GROUP_MEMBER_FMT!("name", "prio", "address"))?; + for member in self.iter() { + writeln!(f, "{member}")?; + } + Ok(()) + } +} +impl Display for GwGroupTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, " ━━━━━━━━━━ Gateway groups ━━━━━━━━━━")?; + for g in self.iter() { + write!(f, "{g}")?; + } + Ok(()) + } +} + +#[cfg(test)] +#[rustfmt::skip] +mod test { + use super::{GwGroup, GwGroupMember, GwGroupTable}; + use crate::ConfigError; + use crate::external::PriorityCommunityTable; + use std::net::IpAddr; + use std::str::FromStr; + + #[test] + fn test_gw_groups() { + let mut gwtable = GwGroupTable::new(); + + // first group + let mut group = GwGroup::new("gw-group-1"); + group.add_member(GwGroupMember::new("gw1", 1, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 2, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 3, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + + // err on duplicate member names or ip addresses + let r = group.add_member(GwGroupMember::new("gw1", 99, IpAddr::from_str("172.128.0.4").unwrap())); + assert!(r.is_err_and(|e| matches!(e, ConfigError::DuplicateMember(_)))); + let r = group.add_member(GwGroupMember::new("gw4", 99, IpAddr::from_str("172.128.0.1").unwrap())); + assert!(r.is_err_and(|e| matches!(e, ConfigError::DuplicateMember(_)))); + gwtable.add_group(group).unwrap(); + + // err on duplicate group + let duped = GwGroup::new("gw-group-1"); + let r = gwtable.add_group(duped); + assert!(r.is_err_and(|e| matches!(e, ConfigError::DuplicateGroup(_)))); + + // second group + let mut group = GwGroup::new("gw-group-2"); + group.add_member(GwGroupMember::new("gw2", 0, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 2, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + gwtable.add_group(group).unwrap(); + println!("{gwtable}"); + + // lookup + let member = gwtable.get_group_member("gw-group-1", "gw1").unwrap(); + assert_eq!(member.name, "gw1"); + assert_eq!(member.priority, 1); + assert_eq!(member.ipaddress, IpAddr::from_str("172.128.0.1").unwrap()); + } + + #[test] + fn test_gw_group_ordering() { + let mut group = GwGroup::new("gw-group-1"); + group.add_member(GwGroupMember::new("gw1", 100, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 90, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 300, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw4", 0, IpAddr::from_str("172.128.0.4").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw5", 100, IpAddr::from_str("172.128.0.5").unwrap())).unwrap(); + group.sort_members(); + + let mut prio = group.members[0].priority; + for m in group.iter() { + assert!(m.priority <= prio); + prio = m.priority; + } + + let mut gwtable = GwGroupTable::new(); + gwtable.add_group(group).unwrap(); + println!("{gwtable}"); + } + + fn build_sample_gw_groups() -> GwGroupTable { + let mut gwtable = GwGroupTable::new(); + + let mut group = GwGroup::new("gw-group-1"); + group.add_member(GwGroupMember::new("gw1", 1, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 2, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 3, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + gwtable.add_group(group).unwrap(); + + let mut group = GwGroup::new("gw-group-2"); + group.add_member(GwGroupMember::new("gw1", 2, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 3, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + gwtable.add_group(group).unwrap(); + + let mut group = GwGroup::new("gw-group-3"); + group.add_member(GwGroupMember::new("gw1", 3, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 1, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + gwtable.add_group(group).unwrap(); + gwtable + } + + fn sample_community_table() -> PriorityCommunityTable { + let mut comtable = PriorityCommunityTable::new(); + comtable.insert(0, "65000:800").unwrap(); + comtable.insert(1, "65000:801").unwrap(); + comtable.insert(2, "65000:802").unwrap(); + comtable.insert(3, "65000:803").unwrap(); + comtable.insert(4, "65000:804").unwrap(); + comtable + } + + #[test] + fn test_bgp_community_setup() { + let gwtable = build_sample_gw_groups(); + let comtable = sample_community_table(); + + println!("{gwtable}"); + println!("{comtable}"); + + let member = gwtable.get_group_member("gw-group-1", "gw1").unwrap(); + let com = comtable.get_community(member.priority).unwrap(); + assert_eq!(com, "65000:801"); + + let member = gwtable.get_group_member("gw-group-2", "gw1").unwrap(); + let com = comtable.get_community(member.priority).unwrap(); + assert_eq!(com, "65000:802"); + + let member = gwtable.get_group_member("gw-group-3", "gw1").unwrap(); + let com = comtable.get_community(member.priority).unwrap(); + assert_eq!(com, "65000:803"); + } +} diff --git a/config/src/external/mod.rs b/config/src/external/mod.rs index aba7e028c..3dffa8ff1 100644 --- a/config/src/external/mod.rs +++ b/config/src/external/mod.rs @@ -3,15 +3,19 @@ //! Dataplane External/API configuration model. This model is the model assumed by the RPC. +pub mod communities; +pub mod gwgroup; pub mod overlay; pub mod underlay; -use derive_builder::Builder; - use crate::internal::device::DeviceConfig; -use crate::internal::device::settings::DeviceSettings; use crate::{ConfigError, ConfigResult}; +use communities::PriorityCommunityTable; +use derive_builder::Builder; +use gwgroup::GwGroupTable; +use gwname::get_gw_name; use overlay::Overlay; +use tracing::{debug, warn}; use underlay::Underlay; /// Alias for a config generation number @@ -20,10 +24,12 @@ pub type GenId = i64; /// The configuration object as seen by the gRPC server #[derive(Builder, Clone, Debug)] pub struct ExternalConfig { - pub genid: GenId, /* configuration generation id (version) */ - pub device: DeviceConfig, /* goes as-is into the internal config */ - pub underlay: Underlay, /* goes as-is into the internal config */ - pub overlay: Overlay, /* VPCs and peerings -- get highly developed in internal config */ + pub genid: GenId, /* configuration generation id (version) */ + pub device: DeviceConfig, /* goes as-is into the internal config */ + pub underlay: Underlay, /* goes as-is into the internal config */ + pub overlay: Overlay, /* VPCs and peerings -- get highly developed in internal config */ + pub gwgroups: GwGroupTable, /* gateway group table */ + pub communities: PriorityCommunityTable, /* priority-to-community table */ } impl ExternalConfig { pub const BLANK_GENID: GenId = 0; @@ -33,15 +39,88 @@ impl ExternalConfig { pub fn new() -> Self { Self { genid: Self::BLANK_GENID, - device: DeviceConfig::new(DeviceSettings::new("Unset")), + device: DeviceConfig::new(), underlay: Underlay::default(), overlay: Overlay::default(), + gwgroups: GwGroupTable::new(), + communities: PriorityCommunityTable::new(), } } + + /// Check the gateway group for a peering + fn check_peering_gwgroup( + gwname: &str, + gwgroup: &str, + peering_name: &str, + gwgroups: &GwGroupTable, + comtable: &PriorityCommunityTable, + ) -> Result, ConfigError> { + debug!("Peering {peering_name} is to be handled by gateway group '{gwgroup}'",); + + // lookup group referred by peering: it must exist + let group = gwgroups + .get_group(gwgroup) + .ok_or_else(|| ConfigError::NoSuchGroup(gwgroup.to_owned()))?; + + // sort out the group. Can't do in place because we don't want to modify the + // external config received + let group = group.sorted(); + + // lookup ourselves in the group; we care about our position (ranking) in the + // group and not about the priority whose absolute value is meaningless. + let Some(pos) = group.get_member_pos(gwname) else { + warn!("Gateway {gwname} is NOT part of group {}", group.name()); + return Ok(None); + }; + // need conversion to u32 as that is the key for the community table + let pos = u32::try_from(pos).map_err(|e| ConfigError::InternalFailure(e.to_string()))?; + + // We should be serving this peering. + debug!("Gateway {gwname} is at position {pos} of {}", group.name()); + + // Get the community corresponding to the position/ordering of this gateway in the group. + // If no community exist for that position, we should fail, although we don't now since + // the community table may not be populated. + // To guarantee that we can always tag with a community in the set of |C| communities, + // the size of a group |G| must be no larger than |C|. + if let Ok(community) = comtable.get_community(pos) { + Ok(Some(community.clone())) + } else { + warn!("No community found for preference {pos}"); + Ok(None) + } + } + + fn validate_peering_gw_groups(&mut self) -> ConfigResult { + let gwname = get_gw_name().unwrap_or_else(|| unreachable!()); + let gwgroups = &self.gwgroups; + let comtable = &self.communities; + for vpc in self.overlay.vpc_table.values_mut() { + for peering in &mut vpc.peerings { + if let Some(gwgroup) = &peering.gwgroup + && let Some(community) = Self::check_peering_gwgroup( + gwname, + gwgroup, + &peering.name, + gwgroups, + comtable, + )? + { + debug!( + "Assigned community {community} to peering {}", + &peering.name + ); + peering.adv_communities.push(community.clone()); + } + } + } + Ok(()) + } pub fn validate(&mut self) -> ConfigResult { self.device.validate()?; self.underlay.validate()?; self.overlay.validate()?; + self.validate_peering_gw_groups()?; // if there are vpcs configured, there MUST be a vtep configured if !self.overlay.vpc_table.is_empty() && self.underlay.vtep.is_none() { @@ -49,6 +128,8 @@ impl ExternalConfig { "Vtep interface configuration", )); } + debug!("Community table mappings:\n{}", self.communities); + debug!("Gateway-groups are:\n{}", self.gwgroups); Ok(()) } } diff --git a/config/src/external/overlay/mod.rs b/config/src/external/overlay/mod.rs index 4ac9cf6a9..59c2e6415 100644 --- a/config/src/external/overlay/mod.rs +++ b/config/src/external/overlay/mod.rs @@ -7,12 +7,12 @@ pub mod tests; pub mod vpc; pub mod vpcpeering; -use crate::external::overlay::vpc::VpcIdMap; -use crate::external::overlay::vpc::VpcTable; -use crate::external::overlay::vpcpeering::VpcManifest; -use crate::external::overlay::vpcpeering::VpcPeeringTable; use crate::{ConfigError, ConfigResult}; use tracing::{debug, error}; +use vpc::VpcIdMap; +use vpc::VpcTable; +use vpcpeering::VpcManifest; +use vpcpeering::VpcPeeringTable; #[derive(Clone, Debug, Default)] pub struct Overlay { @@ -55,6 +55,7 @@ impl Overlay { /* collect peerings of every VPC */ self.vpc_table .collect_peerings(&self.peering_table, &id_map); + self.vpc_table.validate()?; debug!( diff --git a/config/src/external/overlay/tests.rs b/config/src/external/overlay/tests.rs index 4834f86fb..e5d84cb81 100644 --- a/config/src/external/overlay/tests.rs +++ b/config/src/external/overlay/tests.rs @@ -49,7 +49,7 @@ pub mod test { let m1 = build_manifest_vpc1(); let m2 = build_manifest_vpc2(); // build vpc peering with the manifests - VpcPeering::new("VPC-1--VPC-2", m1, m2) + VpcPeering::new("VPC-1--VPC-2", m1, m2, None) } #[test] @@ -377,22 +377,22 @@ pub mod test { let m1 = VpcManifest::new("VPC-1"); let m2 = VpcManifest::new("VPC-2"); - let peering = VpcPeering::new("Peering-1", m1, m2); + let peering = VpcPeering::new("Peering-1", m1, m2, None); peering_table.add(peering).unwrap(); let m1 = VpcManifest::new("VPC-1"); let m2 = VpcManifest::new("VPC-3"); - let peering = VpcPeering::new("Peering-2", m1, m2); + let peering = VpcPeering::new("Peering-2", m1, m2, None); peering_table.add(peering).unwrap(); let m1 = VpcManifest::new("VPC-2"); let m2 = VpcManifest::new("VPC-4"); - let peering = VpcPeering::new("Peering-3", m1, m2); + let peering = VpcPeering::new("Peering-3", m1, m2, None); peering_table.add(peering).unwrap(); let m1 = VpcManifest::new("VPC-1"); let m2 = VpcManifest::new("VPC-4"); - let peering = VpcPeering::new("Peering-4", m1, m2); + let peering = VpcPeering::new("Peering-4", m1, m2, None); peering_table.add(peering).unwrap(); // all peerings of VPC-1 @@ -490,6 +490,7 @@ pub mod test { "VPC-1--VPC-2", man_vpc1_with_vpc2(), man_vpc2(), + None, )) .expect("Should succeed"); @@ -498,6 +499,7 @@ pub mod test { "VPC-1--VPC-3", man_vpc1_with_vpc3(), man_vpc3(), + None, )) .expect("Should succeed"); @@ -506,6 +508,7 @@ pub mod test { "VPC-1--VPC-4", man_vpc1_with_vpc4(), man_vpc4(), + None, )) .expect("Should succeed"); @@ -514,13 +517,14 @@ pub mod test { "VPC-2--VPC-3", man_vpc2_with_vpc3(), man_vpc3(), + None, )) .expect("Should succeed"); assert_eq!(peering_table.len(), 4); /* peering with empty name cannot be added to the table */ - let peering_empty_name = VpcPeering::new("", man_vpc1_with_vpc2(), man_vpc2()); + let peering_empty_name = VpcPeering::new("", man_vpc1_with_vpc2(), man_vpc2(), None); assert_eq!( peering_table.add(peering_empty_name), Err(ConfigError::MissingIdentifier("Peering name")) @@ -529,7 +533,7 @@ pub mod test { /* peering with duplicate name cannot be added to the table */ let peering_duplicate_name = - VpcPeering::new("VPC-1--VPC-2", man_vpc1_with_vpc2(), man_vpc2()); + VpcPeering::new("VPC-1--VPC-2", man_vpc1_with_vpc2(), man_vpc2(), None); assert_eq!( peering_table.add(peering_duplicate_name), Err(ConfigError::DuplicateVpcPeeringId( diff --git a/config/src/external/overlay/vpc.rs b/config/src/external/overlay/vpc.rs index 7f0754f66..306c8a556 100644 --- a/config/src/external/overlay/vpc.rs +++ b/config/src/external/overlay/vpc.rs @@ -25,10 +25,12 @@ use crate::external::overlay::vpcpeering::VpcPeering; /// Most importantly, [`Peering`] has a notion of local and remote, while [`VpcPeering`] is symmetrical. #[derive(Clone, Debug, PartialEq)] pub struct Peering { - pub name: String, /* name of peering */ - pub local: VpcManifest, /* local manifest */ - pub remote: VpcManifest, /* remote manifest */ - pub remote_id: VpcId, + pub name: String, /* name of peering */ + pub local: VpcManifest, /* local manifest */ + pub remote: VpcManifest, /* remote manifest */ + pub remote_id: VpcId, /* Id of peer */ + pub gwgroup: Option, /* gateway group serving this peering */ + pub adv_communities: Vec, /* communities with which to advertise prefixes in this peering */ } #[derive(Clone, Debug, PartialEq, Ord, PartialOrd, Eq)] @@ -80,6 +82,7 @@ impl Vpc { peerings: vec![], }) } + /// Add an [`InterfaceConfig`] to this [`Vpc`] pub fn add_interface_config(&mut self, if_cfg: InterfaceConfig) { self.interfaces.add_interface_config(if_cfg); @@ -98,6 +101,8 @@ impl Vpc { local: local.clone(), remote: remote.clone(), remote_id: remote_id.clone(), + gwgroup: p.gw_group.clone(), + adv_communities: vec![], } }) .collect(); @@ -108,11 +113,13 @@ impl Vpc { debug!("Vpc '{}' has {} peerings", self.name, self.peerings.len()); } } + /// Tell how many peerings this VPC has #[must_use] pub fn num_peerings(&self) -> usize { self.peerings.len() } + /// Tell if the peerings of this VPC have host routes #[must_use] pub fn has_peers_with_host_prefixes(&self) -> bool { @@ -188,10 +195,12 @@ impl VpcTable { pub fn values(&self) -> impl Iterator { self.vpcs.values() } + /// Iterate over [`Vpc`]s in a [`VpcTable`] mutably pub fn values_mut(&mut self) -> impl Iterator { self.vpcs.values_mut() } + /// Collect peerings for all [`Vpc`]s in this [`VpcTable`] pub fn collect_peerings(&mut self, peering_table: &VpcPeeringTable, idmap: &VpcIdMap) { debug!("Collecting peerings for all VPCs.."); @@ -202,6 +211,7 @@ impl VpcTable { pub fn clear_vnis(&mut self) { self.vnis.clear(); } + /// Validate the [`VpcTable`] pub fn validate(&self) -> ConfigResult { for vpc in self.values() { diff --git a/config/src/external/overlay/vpcpeering.rs b/config/src/external/overlay/vpcpeering.rs index 463ae1ce0..31dfc3276 100644 --- a/config/src/external/overlay/vpcpeering.rs +++ b/config/src/external/overlay/vpcpeering.rs @@ -506,17 +506,24 @@ impl VpcManifest { #[derive(Clone, Debug)] pub struct VpcPeering { - pub name: String, /* name of peering (key in table) */ - pub left: VpcManifest, /* manifest for one side of the peering */ - pub right: VpcManifest, /* manifest for the other side */ + pub name: String, /* name of peering (key in table) */ + pub left: VpcManifest, /* manifest for one side of the peering */ + pub right: VpcManifest, /* manifest for the other side */ + pub gw_group: Option, /* name of gateway group */ } impl VpcPeering { #[must_use] - pub fn new(name: &str, left: VpcManifest, right: VpcManifest) -> Self { + pub fn new( + name: &str, + left: VpcManifest, + right: VpcManifest, + gw_group: Option, + ) -> Self { Self { name: name.to_owned(), left, right, + gw_group, } } pub fn validate(&self) -> ConfigResult { diff --git a/config/src/internal/device/mod.rs b/config/src/internal/device/mod.rs index e9dcace29..92b7f6797 100644 --- a/config/src/internal/device/mod.rs +++ b/config/src/internal/device/mod.rs @@ -3,40 +3,27 @@ //! Dataplane configuration model: device -pub mod ports; -pub mod settings; pub mod tracecfg; -use ports::PortConfig; -use settings::DeviceSettings; use tracecfg::TracingConfig; use tracing::{debug, error}; use crate::{ConfigError, ConfigResult}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct DeviceConfig { - pub settings: DeviceSettings, - pub ports: Vec, pub tracing: Option, } impl DeviceConfig { #[must_use] - pub fn new(settings: DeviceSettings) -> Self { - Self { - settings, - ports: vec![], - tracing: None, - } + pub fn new() -> Self { + Self { tracing: None } } pub fn set_tracing(&mut self, tracing: TracingConfig) { self.tracing = Some(tracing); } pub fn validate(&self) -> ConfigResult { debug!("Validating device configuration.."); - if self.settings.hostname.is_empty() { - return Err(ConfigError::MissingIdentifier("Device hostname")); - } if let Some(tracing) = &self.tracing { // DISABLE validation since the set of available tags // is not burnt in the gRPC protobuf schema. diff --git a/config/src/internal/device/ports.rs b/config/src/internal/device/ports.rs deleted file mode 100644 index b662aa80a..000000000 --- a/config/src/internal/device/ports.rs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Open Network Fabric Authors - -//! Physical port configuration - -#[derive(Clone, Debug)] -pub struct PortConfig { - // Todo -} diff --git a/config/src/internal/device/settings.rs b/config/src/internal/device/settings.rs deleted file mode 100644 index 35ee11f73..000000000 --- a/config/src/internal/device/settings.rs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Open Network Fabric Authors - -//! Device settings - -#[derive(Clone, Debug, Default)] - -pub struct DpdkPortConfig {} - -#[derive(Clone, Debug)] - -pub struct KernelPacketConfig {} - -#[derive(Clone, Debug)] -pub enum PacketDriver { - DPDK(DpdkPortConfig), - Kernel(KernelPacketConfig), -} - -#[derive(Clone, Debug)] -pub struct DeviceSettings { - pub hostname: String, - pub driver: PacketDriver, -} - -impl DeviceSettings { - #[must_use] - pub fn new(hostname: &str) -> Self { - Self { - hostname: hostname.to_owned(), - driver: PacketDriver::DPDK(DpdkPortConfig::default()), - } - } - #[must_use] - pub fn set_packet_driver(mut self, driver: PacketDriver) -> Self { - self.driver = driver; - self - } -} diff --git a/config/src/internal/mod.rs b/config/src/internal/mod.rs index caf7ab109..d74c3d3ad 100644 --- a/config/src/internal/mod.rs +++ b/config/src/internal/mod.rs @@ -24,6 +24,7 @@ use crate::internal::routing::frr::Frr; use crate::internal::routing::prefixlist::{PrefixList, PrefixListTable}; use crate::internal::routing::routemap::{RouteMap, RouteMapTable}; use crate::internal::routing::vrf::{VrfConfig, VrfConfigTable}; +use gwname::get_gw_name; #[derive(Clone, Debug)] /* Main internal GW configuration */ @@ -39,11 +40,9 @@ pub struct InternalConfig { impl InternalConfig { #[must_use] pub fn new(dev_cfg: DeviceConfig) -> Self { + let hostname = get_gw_name().unwrap_or_else(|| unreachable!()); // Frr profile is not configurable for the time being - let frr = Frr::new( - routing::frr::FrrProfile::Datacenter, - &dev_cfg.settings.hostname, - ); + let frr = Frr::new(routing::frr::FrrProfile::Datacenter, hostname); Self { dev_cfg, frr, diff --git a/config/src/internal/routing/routemap.rs b/config/src/internal/routing/routemap.rs index 36d87f50c..b55a3b107 100644 --- a/config/src/internal/routing/routemap.rs +++ b/config/src/internal/routing/routemap.rs @@ -43,6 +43,7 @@ pub enum RouteMapSetAction { pub enum Community { None, ASNVAL(u16, u16), + String(String), NoAdvertise, NoExport, NoPeer, diff --git a/config/src/utils/collapse.rs b/config/src/utils/collapse.rs index dab50cf4a..36ecb9cc4 100644 --- a/config/src/utils/collapse.rs +++ b/config/src/utils/collapse.rs @@ -252,6 +252,8 @@ mod tests { local: manifest, remote: manifest_empty.clone(), remote_id: "12345".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; let expected_expose = VpcExpose::empty() diff --git a/k8s-intf/build.rs b/k8s-intf/build.rs index f0477bf9b..42ac471b3 100644 --- a/k8s-intf/build.rs +++ b/k8s-intf/build.rs @@ -68,6 +68,7 @@ fn fixup_types(raw: String) -> String { .replace("b: Option", "b: Option") .replace("d: Option", "d: Option") .replace("p: Option", "p: Option") + .replace("priority: Option", "priority: Option") } fn generate_rust_for_crd(crd_content: &str) -> String { diff --git a/k8s-intf/src/bolero/gateway.rs b/k8s-intf/src/bolero/gateway.rs index 686f535f4..04a4bc7f8 100644 --- a/k8s-intf/src/bolero/gateway.rs +++ b/k8s-intf/src/bolero/gateway.rs @@ -13,8 +13,8 @@ use crate::bolero::LegalValue; use crate::bolero::Normalize; use crate::gateway_agent_crd::{ - GatewayAgentGateway, GatewayAgentGatewayInterfaces, GatewayAgentGatewayLogs, - GatewayAgentGatewayNeighbors, + GatewayAgentGateway, GatewayAgentGatewayGroups, GatewayAgentGatewayInterfaces, + GatewayAgentGatewayLogs, GatewayAgentGatewayNeighbors, }; impl TypeGenerator for LegalValue { @@ -34,9 +34,15 @@ impl TypeGenerator for LegalValue { neighbors.push(d.produce::>()?.0); } + let n_groups = d.gen_usize(Bound::Included(&1), Bound::Included(&4))?; + let mut groups = Vec::new(); + for _ in 0..n_groups { + groups.push(d.produce::>()?.0); + } + Some(LegalValue(GatewayAgentGateway { asn: Some(d.gen_u32(Bound::Included(&1), Bound::Unbounded)?), - groups: None, // FIXME(mvachhar) Add a proper implementation when used + groups: Some(groups), logs: Some(d.produce::>()?.take()), interfaces: Some(interfaces).filter(|i| !i.is_empty()), neighbors: Some(neighbors).filter(|n| !n.is_empty()), @@ -62,7 +68,7 @@ impl Normalize for GatewayAgentGateway { fn normalize(&self) -> Self { GatewayAgentGateway { asn: self.asn, - groups: None, // FIXME(mvachhar) Add a proper implementation when used + groups: self.groups.clone(), logs: self.logs.clone(), interfaces: self .interfaces diff --git a/k8s-intf/src/bolero/gwgroups.rs b/k8s-intf/src/bolero/gwgroups.rs new file mode 100644 index 000000000..c3a3655dc --- /dev/null +++ b/k8s-intf/src/bolero/gwgroups.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Open Network Fabric Authors + +use bolero::{Driver, TypeGenerator}; +use net::ipv4::UnicastIpv4Addr; +use std::ops::Bound; + +use crate::bolero::LegalValue; +use crate::gateway_agent_crd::{ + GatewayAgentGatewayGroups, GatewayAgentGroups, GatewayAgentGroupsMembers, +}; + +impl TypeGenerator for LegalValue { + fn generate(driver: &mut D) -> Option { + let g = GatewayAgentGatewayGroups { + name: driver.produce::(), + priority: Some(driver.gen_u32(Bound::Included(&0), Bound::Included(&10))?), + }; + Some(LegalValue(g)) + } +} + +impl TypeGenerator for GatewayAgentGroupsMembers { + fn generate(driver: &mut D) -> Option { + let gmember = GatewayAgentGroupsMembers { + name: driver.produce::(), + priority: Some(driver.gen_u32(Bound::Included(&0), Bound::Included(&10))?), + vtep_ip: Some(driver.produce::()?.to_string()), + }; + Some(gmember) + } +} + +impl TypeGenerator for GatewayAgentGroups { + fn generate(driver: &mut D) -> Option { + let num_members = driver.gen_usize(Bound::Included(&0), Bound::Included(&10))?; + let mut members = vec![]; + if num_members > 0 { + members.push(driver.produce::()?); + } + Some(GatewayAgentGroups { + members: Some(members), + }) + } +} diff --git a/k8s-intf/src/bolero/mod.rs b/k8s-intf/src/bolero/mod.rs index c552d5b16..078ef00a1 100644 --- a/k8s-intf/src/bolero/mod.rs +++ b/k8s-intf/src/bolero/mod.rs @@ -5,6 +5,7 @@ pub mod bgp; pub mod crd; pub mod expose; pub mod gateway; +pub mod gwgroups; pub mod interface; pub mod logs; pub mod peering; diff --git a/k8s-intf/src/bolero/spec.rs b/k8s-intf/src/bolero/spec.rs index c8c9d0771..12c3dacb8 100644 --- a/k8s-intf/src/bolero/spec.rs +++ b/k8s-intf/src/bolero/spec.rs @@ -10,7 +10,9 @@ use lpm::prefix::Prefix; use crate::bolero::peering::LegalValuePeeringsGenerator; use crate::bolero::{LegalValue, SubnetMap, VpcSubnetMap}; -use crate::gateway_agent_crd::{GatewayAgentGateway, GatewayAgentSpec, GatewayAgentVpcs}; +use crate::gateway_agent_crd::{ + GatewayAgentGateway, GatewayAgentGroups, GatewayAgentSpec, GatewayAgentVpcs, +}; fn extract_subnets(vpcs: &BTreeMap) -> VpcSubnetMap { let mut vpc_subnets = VpcSubnetMap::new(); @@ -32,7 +34,8 @@ fn extract_subnets(vpcs: &BTreeMap) -> VpcSubnetMap { /// /// This does not cover all legal `GatewayAgentSpecs`, /// it is limited by the underlying generators and it generates -/// vpcs and peerings with a fixed name pattern. +/// vpcs and peerings with a fixed name pattern and not all +/// vni combinations are generated. impl TypeGenerator for LegalValue { fn generate(d: &mut D) -> Option { let num_vpcs = d.gen_usize(Bound::Included(&0), Bound::Included(&16))?; @@ -43,11 +46,13 @@ impl TypeGenerator for LegalValue { }; let mut vpcs = BTreeMap::new(); + let vni_base = d.gen_u32(Bound::Included(&1), Bound::Included(&1000))?; for i in 0..num_vpcs { - vpcs.insert( - format!("vpc{i}"), - d.produce::>()?.take(), - ); + let vni_offset = u32::try_from(i).expect("too many vpcs"); + let lv_vpc = d.produce::>()?; + let mut vpc = lv_vpc.take(); + vpc.vni = Some(vni_base + vni_offset); + vpcs.insert(format!("vpc{i}"), vpc); } let vpc_subnet_map = extract_subnets(&vpcs); @@ -60,10 +65,23 @@ impl TypeGenerator for LegalValue { } } + let num_groups = d.gen_usize(Bound::Included(&0), Bound::Included(&6))?; + let mut groups = BTreeMap::new(); + for i in 0..=num_groups { + groups.insert(format!("gwgroup-{i}"), d.produce::()?); + } + + let num_communities = d.gen_usize(Bound::Included(&0), Bound::Included(&9))?; + let mut communities = BTreeMap::new(); + for i in 0..=num_communities { + let community = format!("65000:{}", 100 + i); + communities.insert(i.to_string(), community); + } + Some(LegalValue(GatewayAgentSpec { agent_version: None, - communities: None, // FIXME(mvachhar) Add a proper implementation when used - groups: None, // FIXME(mvachhar) Add a proper implementation when used + groups: Some(groups), + communities: Some(communities), gateway: Some(d.produce::>()?.take()), vpcs: Some(vpcs).filter(|v| !v.is_empty()), peerings: Some(peerings).filter(|p| !p.is_empty()), diff --git a/k8s-intf/src/generated/gateway_agent_crd.rs b/k8s-intf/src/generated/gateway_agent_crd.rs index 828617a4b..00d884b86 100644 --- a/k8s-intf/src/generated/gateway_agent_crd.rs +++ b/k8s-intf/src/generated/gateway_agent_crd.rs @@ -82,7 +82,7 @@ pub struct GatewayAgentGatewayGroups { pub name: Option, /// Priority is the priority of the gateway within the group #[serde(default, skip_serializing_if = "Option::is_none")] - pub priority: Option, + pub priority: Option, } /// Interfaces is a map of interface names to their configurations @@ -143,7 +143,7 @@ pub struct GatewayAgentGroupsMembers { #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub priority: Option, + pub priority: Option, #[serde(default, skip_serializing_if = "Option::is_none", rename = "vtepIP")] pub vtep_ip: Option, } diff --git a/mgmt/Cargo.toml b/mgmt/Cargo.toml index dec161914..5fb394b73 100644 --- a/mgmt/Cargo.toml +++ b/mgmt/Cargo.toml @@ -43,6 +43,7 @@ caps = { workspace = true, default-features = false, features = [] } chrono = { workspace = true } derive_builder = { workspace = true, default-features = false, features = ["default"] } futures = { workspace = true, features = ["default"] } +gwname = { workspace = true } linkme = { workspace = true } multi_index_map = { workspace = true, features = ["serde"] } netdev = { workspace = true } diff --git a/mgmt/src/grpc/server.rs b/mgmt/src/grpc/server.rs index 662c0c507..a18a1ea28 100644 --- a/mgmt/src/grpc/server.rs +++ b/mgmt/src/grpc/server.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use std::sync::Arc; use tonic::{Request, Response, Status}; -use tracing::debug; +use tracing::{debug, error}; use crate::processor::proc::{ConfigChannelRequest, ConfigRequest, ConfigResponse}; use config::converters::grpc::{ @@ -172,7 +172,11 @@ impl ConfigManager for BasicConfigManager { debug!("Received request to apply new config"); // Convert config from gRPC to native external model - let external_config = convert_gateway_config_from_grpc_with_defaults(&grpc_config)?; + let external_config = convert_gateway_config_from_grpc_with_defaults(&grpc_config) + .map_err(|e| { + error!("Failed to process apply config: {e}"); + e + })?; // Create a new GwConfig with this ExternalConfig let gw_config = Box::new(GwConfig::new(external_config)); diff --git a/mgmt/src/processor/confbuild/internal.rs b/mgmt/src/processor/confbuild/internal.rs index 87471ead0..7fd6ad0c6 100644 --- a/mgmt/src/processor/confbuild/internal.rs +++ b/mgmt/src/processor/confbuild/internal.rs @@ -6,6 +6,8 @@ #[allow(unused)] use tracing::{debug, error, warn}; +const IMPORT_VRFS: bool = false; + use config::external::overlay::Overlay; use config::external::overlay::vpc::{Peering, Vpc}; use config::external::overlay::vpcpeering::VpcManifest; @@ -22,7 +24,9 @@ use config::internal::routing::bgp::{BgpConfig, BgpOptions, VrfImports}; use config::internal::routing::prefixlist::{ IpVer, PrefixList, PrefixListAction, PrefixListEntry, PrefixListMatchLen, PrefixListPrefix, }; -use config::internal::routing::routemap::{MatchingPolicy, RouteMap, RouteMapEntry, RouteMapMatch}; +use config::internal::routing::routemap::{ + Community, MatchingPolicy, RouteMap, RouteMapEntry, RouteMapMatch, RouteMapSetAction, +}; use config::internal::routing::statics::StaticRoute; use config::internal::routing::vrf::VrfConfig; use config::{ExternalConfig, GwConfig, InternalConfig}; @@ -91,6 +95,7 @@ fn build_vpc_drop_routes(rmanifest: &VpcManifest) -> Vec { } /// Build AF l2vpn EVPN config for a VPC VRF +#[must_use] fn vpc_bgp_af_l2vpn_evpn(vpc: &Vpc) -> AfL2vpnEvpn { AfL2vpnEvpn::new() .set_adv_all_vni(false) @@ -101,6 +106,7 @@ fn vpc_bgp_af_l2vpn_evpn(vpc: &Vpc) -> AfL2vpnEvpn { } /// Build BGP options for a VPC VRF +#[must_use] fn vpc_bgp_options() -> BgpOptions { BgpOptions::new() .set_network_import_check(false) @@ -117,13 +123,14 @@ struct VpcRoutingConfigIpv4 { /* advertise */ adv_nets: Vec, - adv_rmap: RouteMap, /* one entry per peer */ - adv_plist: PrefixList, /* one prefix list, one entry per peer */ + adv_rmap: RouteMap, + adv_plist: Vec, /* static routes */ sroutes: Vec, } impl VpcRoutingConfigIpv4 { + #[must_use] fn new(vpc: &Vpc) -> Self { Self { import_rmap: RouteMap::new(&vpc.import_rmap_ipv4()), @@ -131,7 +138,7 @@ impl VpcRoutingConfigIpv4 { vrf_imports: VrfImports::new().set_routemap(&vpc.import_rmap_ipv4()), adv_nets: vec![], adv_rmap: RouteMap::new(&vpc.adv_rmap()), - adv_plist: PrefixList::new(&vpc.adv_plist(), IpVer::V4, Some(vpc.adv_plist_desc())), + adv_plist: vec![], sroutes: vec![], } } @@ -139,26 +146,28 @@ impl VpcRoutingConfigIpv4 { /* remote manifest */ let rmanifest = &peer.remote; - /* we import from this vrf */ - self.vrf_imports.add_vrf(peer.remote_id.vrf_name().as_ref()); - - /* build prefix list for the peer from its remote manifest */ - let plist = vpc_import_prefix_list_for_peer(vpc, rmanifest)?; - /* static drops for excluded prefixes (optional) */ let mut statics = build_vpc_drop_routes(rmanifest); self.sroutes.append(&mut statics); /* create import route-map entry */ - let import_rmap_e = RouteMapEntry::new(MatchingPolicy::Permit) - .add_match(RouteMapMatch::Ipv4AddressPrefixList(plist.name.clone())) - .add_match(RouteMapMatch::SrcVrf(peer.remote_id.vrf_name().to_string())); + if IMPORT_VRFS { + /* we import from this vrf */ + self.vrf_imports.add_vrf(peer.remote_id.vrf_name().as_ref()); - /* add entry */ - self.import_rmap.add_entry(None, import_rmap_e)?; + /* build prefix list for the peer from its remote manifest */ + let plist = vpc_import_prefix_list_for_peer(vpc, rmanifest)?; + + let import_rmap_e = RouteMapEntry::new(MatchingPolicy::Permit) + .add_match(RouteMapMatch::Ipv4AddressPrefixList(plist.name.clone())) + .add_match(RouteMapMatch::SrcVrf(peer.remote_id.vrf_name().to_string())); - /* add prefix list to vector */ - self.import_plists.push(plist); + /* add entry */ + self.import_rmap.add_entry(None, import_rmap_e)?; + + /* add prefix list to vector */ + self.import_plists.push(plist); + } /* advertise */ let nets = rmanifest.exposes.iter().flat_map(|e| { @@ -168,7 +177,12 @@ impl VpcRoutingConfigIpv4 { }); self.adv_nets.extend(nets); - /* build adv prefix list */ + /* build adv prefix list and route-map */ + let mut adv_plist = PrefixList::new( + &vpc.adv_plist(&rmanifest.name), + IpVer::V4, + Some(vpc.adv_plist_desc(&rmanifest.name)), + ); for expose in rmanifest.exposes.iter() { let prefixes = expose.public_ips().iter(); let plists = prefixes.map(|prefix_with_ports| { @@ -178,14 +192,28 @@ impl VpcRoutingConfigIpv4 { None, ) }); - self.adv_plist.add_entries(plists)?; + adv_plist.add_entries(plists)?; } + self.adv_plist.push(adv_plist); - /* create adv route-map entry and add it */ - let adv_rmap_e = RouteMapEntry::new(MatchingPolicy::Permit).add_match( - RouteMapMatch::Ipv4AddressPrefixList(self.adv_plist.name.clone()), - ); - self.adv_rmap.add_entry(None, adv_rmap_e)?; + /* collect communities for this peering */ + let communities: Vec<_> = peer + .adv_communities + .iter() + .map(|c| Community::String(c.clone())) + .collect(); + + /* create adv route-map entry matching prefixes and adding communities if needed */ + let mut adv_rmape = RouteMapEntry::new(MatchingPolicy::Permit); + adv_rmape = adv_rmape.add_match(RouteMapMatch::Ipv4AddressPrefixList( + vpc.adv_plist(&rmanifest.name), + )); + if !communities.is_empty() { + adv_rmape = adv_rmape.add_action(RouteMapSetAction::Community(communities, true)); + } + + /* add entry */ + self.adv_rmap.add_entry(None, adv_rmape)?; Ok(()) } @@ -229,7 +257,9 @@ fn vpc_vrf_config(vpc: &Vpc) -> Result { fn vpc_bgp_af_ipv4_unicast(vpc_rconf: &VpcRoutingConfigIpv4) -> AfIpv4Ucast { let mut af = AfIpv4Ucast::new(); - //af.set_vrf_imports(vpc_rconf.vrf_imports.clone()); + if IMPORT_VRFS { + af.set_vrf_imports(vpc_rconf.vrf_imports.clone()); + } af.add_networks(vpc_rconf.adv_nets.clone()); af } @@ -253,10 +283,14 @@ fn build_vpc_internal_config( vpc_rconfig.build_routing_config(vpc)?; bgp.set_af_ipv4unicast(vpc_bgp_af_ipv4_unicast(&vpc_rconfig)); bgp.set_af_l2vpn_evpn(vpc_bgp_af_l2vpn_evpn(vpc)); - internal.add_route_map(vpc_rconfig.import_rmap.clone()); + + if IMPORT_VRFS { + internal.add_route_map(vpc_rconfig.import_rmap.clone()); + internal.add_prefix_lists(vpc_rconfig.import_plists.clone()); + } + internal.add_route_map(vpc_rconfig.adv_rmap.clone()); - internal.add_prefix_lists(vpc_rconfig.import_plists.clone()); - internal.add_prefix_list(vpc_rconfig.adv_plist.clone()); + internal.add_prefix_lists(vpc_rconfig.adv_plist.clone()); vrf_cfg.add_static_routes(vpc_rconfig.sroutes.clone()); } diff --git a/mgmt/src/processor/confbuild/namegen.rs b/mgmt/src/processor/confbuild/namegen.rs index 5cbc0db9b..3fd372023 100644 --- a/mgmt/src/processor/confbuild/namegen.rs +++ b/mgmt/src/processor/confbuild/namegen.rs @@ -48,8 +48,8 @@ pub(crate) trait VpcConfigNames { fn import_rmap_ipv6(&self) -> String; fn import_plist_peer(&self, remote_name: &str) -> String; fn import_plist_peer_desc(&self, remote_name: &str) -> String; - fn adv_plist(&self) -> String; - fn adv_plist_desc(&self) -> String; + fn adv_plist(&self, remote_name: &str) -> String; + fn adv_plist_desc(&self, remote_name: &str) -> String; fn adv_rmap(&self) -> String; } @@ -73,13 +73,19 @@ impl VpcConfigNames for Vpc { fn import_plist_peer_desc(&self, remote_name: &str) -> String { format!("Prefixes of {} reachable by {}", remote_name, self.name) } - fn adv_plist(&self) -> String { - format!("ADV-TO-{}", &self.name.to_uppercase()) + + fn adv_plist(&self, remote_name: &str) -> String { + format!( + "ADV-TO-{}-FOR-{}", + &self.name.to_uppercase(), + remote_name.to_uppercase() + ) } - fn adv_plist_desc(&self) -> String { + fn adv_plist_desc(&self, remote_name: &str) -> String { format!( - "Prefixes allowed to advertised to {}", - &self.name.to_uppercase() + "Prefixes advertised to {} for {}", + &self.name.to_uppercase(), + remote_name.to_uppercase() ) } fn adv_rmap(&self) -> String { diff --git a/mgmt/src/processor/k8s_client.rs b/mgmt/src/processor/k8s_client.rs index bc7c7cca8..4631043a7 100644 --- a/mgmt/src/processor/k8s_client.rs +++ b/mgmt/src/processor/k8s_client.rs @@ -231,12 +231,8 @@ impl K8sClient { tx: Sender, status_update_interval: &std::time::Duration, ) -> Result<(), K8sClientError> { - // Clone this here so that the closure does not try to borrow self - // and cause K8sClient to not be Send for 'static but only a specific - // lifetime - let hostname = self.hostname.clone(); loop { - update_gateway_status(&hostname, &tx).await; + update_gateway_status(&self.hostname, &tx).await; tokio::time::sleep(*status_update_interval).await; } } diff --git a/mgmt/src/tests/mgmt.rs b/mgmt/src/tests/mgmt.rs index d994c30b9..82ef93156 100644 --- a/mgmt/src/tests/mgmt.rs +++ b/mgmt/src/tests/mgmt.rs @@ -5,6 +5,12 @@ #[allow(dead_code)] pub mod test { use caps::Capability::CAP_NET_ADMIN; + use config::external::communities::PriorityCommunityTable; + use config::external::gwgroup::GwGroup; + use config::external::gwgroup::GwGroupMember; + use config::external::gwgroup::GwGroupTable; + + use fixin::wrap; use lpm::prefix::Prefix; use nat::stateful::NatAllocatorWriter; use nat::stateless::NatTablesWriter; @@ -15,6 +21,7 @@ pub mod test { use std::net::Ipv4Addr; use std::str::FromStr; use test_utils::with_caps; + use test_utils::with_gw_name; use tracectl::get_trace_ctl; use tracing::error; use tracing_test::traced_test; @@ -28,9 +35,6 @@ pub mod test { use config::external::underlay::Underlay; use config::internal::device::DeviceConfig; - use config::internal::device::settings::DeviceSettings; - use config::internal::device::settings::KernelPacketConfig; - use config::internal::device::settings::PacketDriver; use config::internal::interfaces::interface::{ IfEthConfig, IfVtepConfig, InterfaceConfig, InterfaceType, }; @@ -48,7 +52,7 @@ pub mod test { use tracing::debug; use stats::VpcMapName; - use stats::VpcStatsStore; // <-- added + use stats::VpcStatsStore; use vpcmap::map::VpcMapWriter; /* OVERLAY config sample builders */ @@ -123,6 +127,7 @@ pub mod test { "VPC-1--VPC-2", man_vpc1_with_vpc2(), man_vpc2_with_vpc1(), + Some("gw-group-1".to_string()), )) .expect("Should succeed"); @@ -131,6 +136,7 @@ pub mod test { "VPC-1--VPC-3", man_vpc1_with_vpc3(), man_vpc3_with_vpc1(), + Some("gw-group-1".to_string()), )) .expect("Should succeed"); @@ -145,12 +151,7 @@ pub mod test { /* DEVICE configuration */ fn sample_device_config() -> DeviceConfig { - /* device settings */ - let settings = DeviceSettings::new("GW1") - .set_packet_driver(PacketDriver::Kernel(KernelPacketConfig {})); - - /* device config */ - DeviceConfig::new(settings) + DeviceConfig::new() } /* UNDERLAY, default VRF BGP AF configs */ @@ -311,6 +312,32 @@ pub mod test { } } + #[rustfmt::skip] + fn sample_gw_groups() -> GwGroupTable { + let mut gwt = GwGroupTable::new(); + let mut group = GwGroup::new("gw-group-1"); + group.add_member(GwGroupMember::new("gw1", 1, IpAddr::from_str("172.128.0.1").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw2", 2, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 3, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + gwt.add_group(group).unwrap(); + + let mut group = GwGroup::new("gw-group-2"); + group.add_member(GwGroupMember::new("gw2", 2, IpAddr::from_str("172.128.0.2").unwrap())).unwrap(); + group.add_member(GwGroupMember::new("gw3", 1, IpAddr::from_str("172.128.0.3").unwrap())).unwrap(); + gwt.add_group(group).unwrap(); + gwt + } + + fn sample_community_table() -> PriorityCommunityTable { + let mut comtable = PriorityCommunityTable::new(); + comtable.insert(0, "65000:800").unwrap(); + comtable.insert(1, "65000:801").unwrap(); + comtable.insert(2, "65000:802").unwrap(); + comtable.insert(3, "65000:803").unwrap(); + comtable.insert(4, "65000:804").unwrap(); + comtable + } + /* build sample external config as it would be received via gRPC */ pub fn sample_external_config() -> ExternalConfig { /* build sample DEVICE config and add it to config */ @@ -322,19 +349,26 @@ pub mod test { /* build sample OVERLAY config (VPCs and peerings) and add it to config */ let overlay = sample_overlay(); + /* build sample gateway groups */ + let groups = sample_gw_groups(); + + /* build sample community table */ + let comtable = sample_community_table(); + /* assemble external config */ - let mut external_builder = ExternalConfigBuilder::default(); - external_builder.genid(1); - external_builder.device(device_cfg); - external_builder.underlay(underlay); - external_builder.overlay(overlay); - external_builder.build().expect("Should succeed") - - /* set VTEP configuration: FIXME, need to accommodate this to internal model */ - //let vtep = VtepConfig::new(loopback, Mac::from([0x2, 0x0, 0x0, 0x0, 0xaa, 0xbb])); + ExternalConfigBuilder::default() + .genid(1) + .device(device_cfg) + .underlay(underlay) + .overlay(overlay) + .gwgroups(groups) + .communities(comtable) + .build() + .expect("Should succeed") } #[traced_test] + #[wrap(with_gw_name())] #[test] fn check_frr_config() { /* Not really a test but a tool to check generated FRR configs given a gateway config */ @@ -352,6 +386,7 @@ pub mod test { } #[tokio::test] + #[wrap(with_gw_name())] #[fixin::wrap(with_caps([CAP_NET_ADMIN]))] async fn test_sample_config() { get_trace_ctl() @@ -360,6 +395,7 @@ pub mod test { /* build sample external config */ let external = sample_external_config(); + println!("External config is:\n{external:#?}"); /* build a gw config from a sample external config */ let config = GwConfig::new(external); diff --git a/nat/Cargo.toml b/nat/Cargo.toml index 85822ae30..bd20cc35c 100644 --- a/nat/Cargo.toml +++ b/nat/Cargo.toml @@ -30,6 +30,8 @@ tracing = { workspace = true } [dev-dependencies] # internal +fixin = { workspace = true } +test-utils = { workspace = true } lpm = { workspace = true, features = ["testing"] } net = { workspace = true, features = ["bolero"] } tracectl = { workspace = true } diff --git a/nat/src/stateful/apalloc/test_alloc.rs b/nat/src/stateful/apalloc/test_alloc.rs index 1c7c3db8f..f376d8d67 100644 --- a/nat/src/stateful/apalloc/test_alloc.rs +++ b/nat/src/stateful/apalloc/test_alloc.rs @@ -140,12 +140,16 @@ mod context { local: manifest1.clone(), remote: manifest2.clone(), remote_id: "12345".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; let peering2 = Peering { name: "test_peering2".into(), local: manifest2, remote: manifest1, remote_id: "67890".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; // VPC-1 diff --git a/nat/src/stateful/test.rs b/nat/src/stateful/test.rs index 076991445..6ad0da72f 100644 --- a/nat/src/stateful/test.rs +++ b/nat/src/stateful/test.rs @@ -7,6 +7,8 @@ mod tests { use crate::stateful::NatAllocatorWriter; use concurrency::sync::Arc; use config::external::ExternalConfigBuilder; + use config::external::communities::PriorityCommunityTable; + use config::external::gwgroup::GwGroupTable; use config::external::overlay::Overlay; use config::external::overlay::vpc::{Vpc, VpcTable}; use config::external::overlay::vpcpeering::{ @@ -14,12 +16,13 @@ mod tests { }; use config::external::underlay::Underlay; use config::internal::device::DeviceConfig; - use config::internal::device::settings::DeviceSettings; + use config::internal::interfaces::interface::{IfVtepConfig, InterfaceConfig, InterfaceType}; use config::internal::routing::bgp::BgpConfig; use config::internal::routing::vrf::VrfConfig; use config::{ConfigError, GwConfig}; use etherparse::Icmpv4Type; + use fixin::wrap; use net::buffer::{PacketBufferMut, TestBuffer}; use net::eth::mac::Mac; use net::headers::{ @@ -43,6 +46,7 @@ mod tests { use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; use std::time::Duration; + use test_utils::with_gw_name; use tracectl::get_trace_ctl; use tracing_test::traced_test; @@ -60,7 +64,7 @@ mod tests { // Use a default configuration to build a valid GwConfig, the details are not really relevant to // our tests fn build_sample_config(overlay: Overlay) -> GwConfig { - let device_config = DeviceConfig::new(DeviceSettings::new("sample")); + let device_config = DeviceConfig::new(); let vtep = InterfaceConfig::new( "vtep", @@ -86,6 +90,9 @@ mod tests { external_builder.device(device_config); external_builder.underlay(underlay); external_builder.overlay(overlay); + external_builder.gwgroups(GwGroupTable::new()); + external_builder.communities(PriorityCommunityTable::new()); + let external_config = external_builder .build() .expect("Failed to build external config"); @@ -245,11 +252,11 @@ mod tests { let mut manifest43 = VpcManifest::new("VPC-4"); add_expose(&mut manifest43, expose431); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); - let peering31 = VpcPeering::new("VPC-3--VPC-1", manifest31, manifest13); - let peering14 = VpcPeering::new("VPC-1--VPC-4", manifest14, manifest41); - let peering24 = VpcPeering::new("VPC-2--VPC-4", manifest24, manifest42); - let peering34 = VpcPeering::new("VPC-3--VPC-4", manifest34, manifest43); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); + let peering31 = VpcPeering::new("VPC-3--VPC-1", manifest31, manifest13, None); + let peering14 = VpcPeering::new("VPC-1--VPC-4", manifest14, manifest41, None); + let peering24 = VpcPeering::new("VPC-2--VPC-4", manifest24, manifest42, None); + let peering34 = VpcPeering::new("VPC-3--VPC-4", manifest34, manifest43, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -286,7 +293,7 @@ mod tests { let mut manifest21 = VpcManifest::new("VPC-2"); add_expose(&mut manifest21, expose211); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -344,6 +351,7 @@ mod tests { #[test] #[traced_test] + #[wrap(with_gw_name())] #[allow(clippy::too_many_lines)] fn test_full_config() { let mut config = build_sample_config(build_overlay_4vpcs()); @@ -548,7 +556,7 @@ mod tests { let mut manifest21 = VpcManifest::new("VPC-2"); add_expose(&mut manifest21, expose211); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -557,6 +565,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_full_config_unidirectional_nat() { let mut config = build_sample_config(build_overlay_2vpcs_unidirectional_nat()); @@ -635,7 +644,7 @@ mod tests { let mut manifest21 = VpcManifest::new("VPC-2"); add_expose(&mut manifest21, expose211); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -644,6 +653,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_full_config_no_nat() { let mut config = build_sample_config(build_overlay_2vpcs_no_nat()); @@ -742,6 +752,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_icmp_echo_nat() { let mut config = build_sample_config(build_overlay_2vpcs()); @@ -839,6 +850,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_icmp_echo_unidirectional_nat() { let mut config = build_sample_config(build_overlay_2vpcs_unidirectional_nat()); @@ -987,6 +999,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_icmp_error_nat() { let mut config = build_sample_config(build_overlay_2vpcs()); @@ -1135,7 +1148,7 @@ mod tests { let mut manifest21 = VpcManifest::new("VPC-2"); add_expose(&mut manifest21, expose21); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); // VPC-2 (no NAT) <-> VPC-3 (NAT) let expose32 = VpcExpose::empty() @@ -1150,7 +1163,7 @@ mod tests { let mut manifest32 = VpcManifest::new("VPC-3"); add_expose(&mut manifest32, expose32); - let peering23 = VpcPeering::new("VPC-2--VPC-3", manifest23, manifest32); + let peering23 = VpcPeering::new("VPC-2--VPC-3", manifest23, manifest32, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).unwrap(); @@ -1230,11 +1243,11 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[allow(clippy::too_many_lines)] fn test_full_config_unidirectional_nat_overlapping_destination() { - get_trace_ctl() - .setup_from_string("vpc-routing=debug,flow-lookup=debug,stateful-nat=debug") - .unwrap(); + let tctl = get_trace_ctl(); + let _ = tctl.setup_from_string("vpc-routing=debug,flow-lookup=debug,stateful-nat=debug"); let mut config = build_sample_config(build_overlay_3vpcs_unidirectional_nat_overlapping_addr()); @@ -1493,7 +1506,7 @@ mod tests { let mut manifest1_2 = VpcManifest::new("VPC-2"); add_expose(&mut manifest1_2, expose1_2); - let peering1 = VpcPeering::new("VPC-1--VPC-2--1", manifest1_1, manifest1_2); + let peering1 = VpcPeering::new("VPC-1--VPC-2--1", manifest1_1, manifest1_2, None); // Peering 2 - Overlap with Peering 1 @@ -1509,7 +1522,7 @@ mod tests { let mut manifest2_2 = VpcManifest::new("VPC-2"); add_expose(&mut manifest2_2, expose2_2); - let peering2 = VpcPeering::new("VPC-1--VPC-2--2", manifest2_1, manifest2_2); + let peering2 = VpcPeering::new("VPC-1--VPC-2--2", manifest2_1, manifest2_2, None); // Peering table @@ -1522,6 +1535,7 @@ mod tests { #[test] #[traced_test] + #[wrap(with_gw_name())] #[allow(clippy::too_many_lines)] fn test_full_config_unidirectional_nat_overlapping_exposes_for_single_peering() { let mut config = diff --git a/nat/src/stateless/setup/mod.rs b/nat/src/stateless/setup/mod.rs index c19aa801e..b9b7b39ff 100644 --- a/nat/src/stateless/setup/mod.rs +++ b/nat/src/stateless/setup/mod.rs @@ -173,6 +173,8 @@ mod tests { local: manifest1, remote: manifest2, remote_id: "12345".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; let src_vni = Vni::new_checked(100).unwrap(); diff --git a/nat/src/stateless/test.rs b/nat/src/stateless/test.rs index 85bfc04b1..ebfec7f8d 100644 --- a/nat/src/stateless/test.rs +++ b/nat/src/stateless/test.rs @@ -7,6 +7,8 @@ mod tests { use config::GwConfig; use config::external::ExternalConfigBuilder; + use config::external::communities::PriorityCommunityTable; + use config::external::gwgroup::GwGroupTable; use config::external::overlay::Overlay; use config::external::overlay::vpc::{Peering, Vpc, VpcTable}; use config::external::overlay::vpcpeering::{ @@ -14,7 +16,6 @@ mod tests { }; use config::external::underlay::Underlay; use config::internal::device::DeviceConfig; - use config::internal::device::settings::DeviceSettings; use config::internal::interfaces::interface::InterfaceConfig; use config::internal::interfaces::interface::{IfVtepConfig, InterfaceType}; use config::internal::routing::bgp::BgpConfig; @@ -24,6 +25,7 @@ mod tests { use crate::stateless::setup::build_nat_configuration; use crate::stateless::setup::tables::{NatTables, PerVniTable}; + use fixin::wrap; use lpm::prefix::{PortRange, PrefixWithOptionalPorts}; use net::buffer::PacketBufferMut; use net::eth::mac::Mac; @@ -40,6 +42,7 @@ mod tests { use pipeline::NetworkFunction; use std::net::Ipv4Addr; use std::str::FromStr; + use test_utils::with_gw_name; use tracing_test::traced_test; fn addr_v4(addr: &str) -> Ipv4Addr { @@ -217,12 +220,16 @@ mod tests { local: manifest1.clone(), remote: manifest2.clone(), remote_id: "12345".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; let peering2 = Peering { name: "test_peering2".into(), local: manifest2, remote: manifest1, remote_id: "67890".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; // This code is extremely convoluted @@ -503,11 +510,11 @@ mod tests { let mut manifest43 = VpcManifest::new("VPC-4"); add_expose(&mut manifest43, expose431); - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); - let peering31 = VpcPeering::new("VPC-3--VPC-1", manifest31, manifest13); - let peering14 = VpcPeering::new("VPC-1--VPC-4", manifest14, manifest41); - let peering24 = VpcPeering::new("VPC-2--VPC-4", manifest24, manifest42); - let peering34 = VpcPeering::new("VPC-3--VPC-4", manifest34, manifest43); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); + let peering31 = VpcPeering::new("VPC-3--VPC-1", manifest31, manifest13, None); + let peering14 = VpcPeering::new("VPC-1--VPC-4", manifest14, manifest41, None); + let peering24 = VpcPeering::new("VPC-2--VPC-4", manifest24, manifest42, None); + let peering34 = VpcPeering::new("VPC-3--VPC-4", manifest34, manifest43, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -525,7 +532,7 @@ mod tests { // configuration is not really relevant to our tests, we just want a valid GwConfig object to // work with. fn build_gwconfig_from_overlay(overlay: Overlay) -> GwConfig { - let device_config = DeviceConfig::new(DeviceSettings::new("sample")); + let device_config = DeviceConfig::new(); let vtep = InterfaceConfig::new( "vtep", @@ -551,6 +558,8 @@ mod tests { external_builder.device(device_config); external_builder.underlay(underlay); external_builder.overlay(overlay); + external_builder.gwgroups(GwGroupTable::new()); + external_builder.communities(PriorityCommunityTable::new()); let external_config = external_builder .build() .expect("Failed to build external config"); @@ -582,6 +591,7 @@ mod tests { #[test] #[traced_test] + #[wrap(with_gw_name())] #[allow(clippy::too_many_lines)] fn test_full_config() { let mut config = build_sample_config(); @@ -760,7 +770,7 @@ mod tests { add_expose(&mut manifest21, expose); } - let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21); + let peering12 = VpcPeering::new("VPC-1--VPC-2", manifest12, manifest21, None); let mut peering_table = VpcPeeringTable::new(); peering_table.add(peering12).expect("Failed to add peering"); @@ -807,6 +817,7 @@ mod tests { } #[test] + #[wrap(with_gw_name())] #[traced_test] fn test_config_with_port_ranges_basic() { let expose1 = VpcExpose::empty() @@ -872,6 +883,7 @@ mod tests { #[test] #[traced_test] + #[wrap(with_gw_name())] #[allow(clippy::too_many_lines)] fn test_config_with_port_ranges_complex() { let expose1 = VpcExpose::empty() diff --git a/pkt-meta/src/dst_vpcd_lookup/setup.rs b/pkt-meta/src/dst_vpcd_lookup/setup.rs index 25e386c06..a0ca05fab 100644 --- a/pkt-meta/src/dst_vpcd_lookup/setup.rs +++ b/pkt-meta/src/dst_vpcd_lookup/setup.rs @@ -216,12 +216,16 @@ mod tests { local: manifest1.clone(), remote: manifest2.clone(), remote_id: "12345".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; let peering2 = Peering { name: "test_peering2".into(), local: manifest2, remote: manifest1, remote_id: "67890".try_into().expect("Failed to create VPC ID"), + gwgroup: None, + adv_communities: vec![], }; let mut vpctable = VpcTable::new(); @@ -333,24 +337,32 @@ mod tests { local: manifest12.clone(), remote: manifest21.clone(), remote_id: "VPC02".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; let peering21 = Peering { name: "VPC-2--VPC-1".into(), local: manifest21.clone(), remote: manifest12.clone(), remote_id: "VPC01".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; let peering23 = Peering { name: "VPC-2--VPC-3".into(), local: manifest23.clone(), remote: manifest32.clone(), remote_id: "VPC03".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; let peering32 = Peering { name: "VPC-3--VPC-2".into(), local: manifest32.clone(), remote: manifest23.clone(), remote_id: "VPC02".try_into().unwrap(), + gwgroup: None, + adv_communities: vec![], }; let mut vpc_table = VpcTable::new(); diff --git a/routing/src/frr/renderer/routemap.rs b/routing/src/frr/renderer/routemap.rs index 04da6d856..b7c32f0a5 100644 --- a/routing/src/frr/renderer/routemap.rs +++ b/routing/src/frr/renderer/routemap.rs @@ -21,7 +21,8 @@ impl Rendered for Community { fn rendered(&self) -> String { match self { Community::None => "none".to_string(), - Community::ASNVAL(asn, val) => format!("{asn}{val}"), + Community::ASNVAL(asn, val) => format!("{asn}:{val}"), + Community::String(value) => value.clone(), Community::NoAdvertise => "no-advertise".to_string(), Community::NoPeer => "no-peer".to_string(), Community::NoExport => "no-export".to_string(), @@ -172,7 +173,12 @@ mod tests { "NHOP-PLIST".to_string(), )) .add_action(RouteMapSetAction::Community( - vec![Community::NoExport, Community::LocalAs], + vec![ + Community::NoExport, + Community::LocalAs, + Community::ASNVAL(100, 1), + Community::String("65000:8001".to_string()), + ], true, )); rmap.add_entry(None, entry).unwrap();