use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use crate::{
BaseInterface, ErrorKind, Interface, InterfaceType, MergedInterface,
MergedInterfaces, NmstateError, Routes,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct VrfInterface {
#[serde(flatten)]
pub base: BaseInterface,
#[serde(skip_serializing_if = "Option::is_none")]
pub vrf: Option<VrfConfig>,
}
impl Default for VrfInterface {
fn default() -> Self {
Self {
base: BaseInterface {
iface_type: InterfaceType::Vrf,
..Default::default()
},
vrf: None,
}
}
}
impl VrfInterface {
pub fn new() -> Self {
Self::default()
}
pub fn ports(&self) -> Option<Vec<&str>> {
self.vrf
.as_ref()
.and_then(|vrf_conf| vrf_conf.port.as_ref())
.map(|ports| ports.as_slice().iter().map(|p| p.as_str()).collect())
}
pub(crate) fn sanitize(
&mut self,
is_desired: bool,
) -> Result<(), NmstateError> {
if is_desired && let Some(mac) = self.base.mac_address.as_ref() {
log::warn!(
"Ignoring MAC address {mac} of VRF interface {} as it is a \
layer 3(IP) interface",
self.base.name.as_str()
);
}
self.base.mac_address = None;
if self.base.accept_all_mac_addresses == Some(false) {
self.base.accept_all_mac_addresses = None;
}
if let Some(ports) = self.vrf.as_mut().and_then(|c| c.port.as_mut()) {
ports.sort();
}
Ok(())
}
pub(crate) fn merge_table_id(
&mut self,
current: Option<&Interface>,
) -> Result<(), NmstateError> {
if self
.vrf
.as_ref()
.and_then(|v| v.table_id)
.unwrap_or_default()
== 0
{
if let Some(cur_table_id) =
if let Some(Interface::Vrf(vrf_iface)) = current {
vrf_iface.vrf.as_ref().and_then(|v| v.table_id)
} else {
None
}
{
if let Some(vrf_conf) = self.vrf.as_mut() {
vrf_conf.table_id = Some(cur_table_id);
}
} else {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Route table ID undefined or 0 is not allowed for new \
VRF interface {}",
self.base.name
),
);
log::error!("{e}");
return Err(e);
}
}
Ok(())
}
pub(crate) fn change_port_name(
&mut self,
origin_name: &str,
new_name: String,
) {
if let Some(port_name) = self
.vrf
.as_mut()
.and_then(|vrf_conf| vrf_conf.port.as_mut())
.and_then(|ports| {
ports
.iter_mut()
.find(|port_name| port_name.as_str() == origin_name)
})
{
*port_name = new_name;
}
}
pub(crate) fn remove_port(&mut self, port_to_remove: &str) {
if let Some(ports) = self
.vrf
.as_mut()
.and_then(|vrf_conf| vrf_conf.port.as_mut())
{
ports.retain(|port_name| port_name != port_to_remove);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[non_exhaustive]
#[serde(deny_unknown_fields)]
pub struct VrfConfig {
#[serde(alias = "ports", skip_serializing_if = "Option::is_none")]
pub port: Option<Vec<String>>,
#[serde(
rename = "route-table-id",
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::deserializer::option_u32_or_string"
)]
pub table_id: Option<u32>,
}
impl MergedInterface {
pub(crate) fn post_inter_ifaces_process_vrf(
&mut self,
) -> Result<(), NmstateError> {
if !self.merged.is_absent() {
if let Some(Interface::Vrf(apply_iface)) = self.for_apply.as_mut() {
apply_iface.merge_table_id(self.current.as_ref())?;
}
if let Some(Interface::Vrf(verify_iface)) = self.for_verify.as_mut()
{
verify_iface.merge_table_id(self.current.as_ref())?;
}
}
Ok(())
}
}
impl Routes {
pub(crate) fn resolve_vrf_name(
&mut self,
merged_ifaces: &MergedInterfaces,
) -> Result<(), NmstateError> {
let vrf_names_down: HashSet<&str> = merged_ifaces
.kernel_ifaces
.values()
.filter(|merged_iface| merged_iface.merged.is_down())
.filter_map(|merged_iface| {
if merged_iface.merged.iface_type() == InterfaceType::Vrf {
Some(merged_iface.merged.name())
} else {
None
}
})
.collect();
let vrf_names_absent: HashSet<&str> = merged_ifaces
.kernel_ifaces
.values()
.filter(|merged_iface| merged_iface.merged.is_absent())
.filter_map(|merged_iface| {
if merged_iface.merged.iface_type() == InterfaceType::Vrf {
Some(merged_iface.merged.name())
} else {
None
}
})
.collect();
let vrf_name_to_table_id: HashMap<&str, u32> = merged_ifaces
.kernel_ifaces
.values()
.filter(|merged_iface| merged_iface.merged.is_up())
.filter_map(|merged_iface| {
if let Interface::Vrf(vrf_iface) = &merged_iface.merged {
vrf_iface.vrf.as_ref().and_then(|v| v.table_id).map(
|table_id| (vrf_iface.base.name.as_str(), table_id),
)
} else {
None
}
})
.collect();
if let Some(config_routes) = self.config.as_mut() {
for rt in config_routes.iter_mut() {
let vrf_name = if let Some(n) = rt.vrf_name.as_deref() {
n
} else {
continue;
};
if vrf_names_down.contains(vrf_name) {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"VRF defined in Route '{rt}' is marked as 'state: \
down'"
),
));
}
if vrf_names_absent.contains(vrf_name) {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"VRF defined in Route '{rt}' is marked as 'state: \
absent'"
),
));
}
let table_id = if let Some(t) =
vrf_name_to_table_id.get(vrf_name)
{
*t
} else {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!("VRF defined in Route '{rt}' does not exist"),
));
};
if let Some(des_table_id) = rt.table_id
&& des_table_id != table_id
{
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Route '{rt}' has both table id and VRF name \
defined, but desired table ID is {} while table \
ID for VRF {} is {}",
des_table_id, vrf_name, table_id
),
));
}
rt.table_id = Some(table_id);
}
}
Ok(())
}
}