use crate::ErrorKind::InvalidArgument;
use crate::NmstateError;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::iter::FromIterator;
use std::str::FromStr;
pub const OVN_BRIDGE_MAPPINGS: &str = "ovn-bridge-mappings";
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(deny_unknown_fields)]
pub struct OvnConfiguration {
#[serde(
rename = "bridge-mappings",
skip_serializing_if = "Option::is_none"
)]
pub bridge_mappings: Option<Vec<OvnBridgeMapping>>,
}
impl OvnConfiguration {
pub fn is_none(&self) -> bool {
self.bridge_mappings.is_none()
}
pub fn sanitize(&self) -> Result<(), NmstateError> {
let desired_mappings: &Vec<OvnBridgeMapping> =
&self.clone().bridge_mappings.unwrap_or_default();
Self::sanitize_unique_localnet_keys(desired_mappings)?;
Self::sanitize_mapping_attributes(desired_mappings)?;
Ok(())
}
fn sanitize_unique_localnet_keys(
desired_mappings: &Vec<OvnBridgeMapping>,
) -> Result<(), NmstateError> {
let localnet_keys: HashSet<String> = HashSet::from_iter(
desired_mappings
.iter()
.map(|mapping| mapping.clone().localnet),
);
if localnet_keys.len() != desired_mappings.len() {
const DUPLICATED_LOCALNET_KEYS: &str =
"Duplicated `localnet` keys in the provided ovn.bridge-mappings";
return Err(NmstateError::new(
InvalidArgument,
DUPLICATED_LOCALNET_KEYS.to_string(),
));
}
Ok(())
}
fn sanitize_mapping_attributes(
desired_mappings: &Vec<OvnBridgeMapping>,
) -> Result<(), NmstateError> {
for mapping in desired_mappings {
mapping.sanitize()?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct MergedOvnConfiguration {
pub(crate) desired: OvnConfiguration,
pub(crate) current: OvnConfiguration,
pub(crate) bridge_mappings: Vec<OvnBridgeMapping>,
pub(crate) mappings_ext_id_value: Option<String>,
}
impl MergedOvnConfiguration {
pub(crate) fn new(
desired: OvnConfiguration,
current: OvnConfiguration,
) -> Result<Self, NmstateError> {
desired.sanitize()?;
let current_mappings: Vec<OvnBridgeMapping> =
current.bridge_mappings.clone().unwrap_or_default();
let mut indexed_current_mappings: HashMap<String, OvnBridgeMapping> =
HashMap::new();
for mapping in current_mappings.clone() {
indexed_current_mappings
.insert(mapping.clone().localnet, mapping.clone());
}
if let Some(mappings) = desired.bridge_mappings.clone() {
for mapping in &mappings {
indexed_current_mappings
.insert(mapping.clone().localnet, mapping.clone());
}
}
let ovn_bridge_mappings: Vec<OvnBridgeMapping> =
indexed_current_mappings
.clone()
.iter()
.filter(|(_, v)| {
v.state.unwrap_or_default()
== OvnBridgeMappingState::Present
})
.map(|(_, v)| v.clone())
.collect();
if desired.clone().bridge_mappings.unwrap_or(Vec::new())
!= current_mappings
{
let updated_ovn_bridge_mappings_ext_ids_value: Option<String> =
match ovn_bridge_mappings.is_empty() {
true => Some("".to_string()),
false => Some(ovn_bridge_mappings_to_string(
ovn_bridge_mappings.clone(),
)),
};
return Ok(Self {
desired,
current,
bridge_mappings: ovn_bridge_mappings,
mappings_ext_id_value:
updated_ovn_bridge_mappings_ext_ids_value,
});
}
Ok(Self {
desired,
current,
bridge_mappings: ovn_bridge_mappings,
mappings_ext_id_value: None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OvnBridgeMapping {
pub localnet: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<OvnBridgeMappingState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bridge: Option<String>,
}
impl OvnBridgeMapping {
pub fn sanitize(&self) -> Result<(), NmstateError> {
if self.state.unwrap_or_default() == OvnBridgeMappingState::Present
&& self.bridge.is_none()
{
return Err(
NmstateError::new(
InvalidArgument,
format!(
"mapping for `localnet` key {} missing the `bridge` attribute",
self.localnet)));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OvnBridgeMappingError {
mapping_wannabe: String,
}
impl std::error::Error for OvnBridgeMappingError {}
impl OvnBridgeMappingError {
fn new(reason: &str) -> Self {
Self {
mapping_wannabe: reason.to_string(),
}
}
}
impl fmt::Display for OvnBridgeMappingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"expected `<localnet>:<bridge>`, got: {}",
self.mapping_wannabe
)
}
}
impl FromStr for OvnBridgeMapping {
type Err = OvnBridgeMappingError;
fn from_str(s: &str) -> Result<OvnBridgeMapping, OvnBridgeMappingError> {
let vec: Vec<&str> = s.split(':').collect();
if vec.len() != 2 {
return Err(OvnBridgeMappingError::new(s));
}
let physnet: String = vec[0].to_string();
let bridge: String = vec[1].to_string();
if physnet.is_empty() || bridge.is_empty() {
return Err(OvnBridgeMappingError::new(s));
}
Ok(OvnBridgeMapping {
localnet: physnet,
bridge: Some(bridge),
state: Some(OvnBridgeMappingState::Present),
})
}
}
impl ToString for OvnBridgeMapping {
fn to_string(&self) -> String {
format!(
"{}:{}",
self.localnet,
self.bridge.clone().unwrap_or("".to_string())
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", deny_unknown_fields)]
#[non_exhaustive]
pub enum OvnBridgeMappingState {
Present,
Absent,
}
impl Default for OvnBridgeMappingState {
fn default() -> Self {
Self::Present
}
}
pub fn ovn_bridge_mappings_to_string(
ovn_bridge_mappings: Vec<OvnBridgeMapping>,
) -> String {
if ovn_bridge_mappings.is_empty() {
return "".to_string();
}
ovn_bridge_mappings
.iter()
.filter(|mapping| mapping.bridge.is_some())
.map(|mapping| mapping.to_string())
.fold("".to_string(), |mappings, mapping| {
if mappings.is_empty() {
mapping
} else {
format!("{mappings},{mapping}")
}
})
}