Add Rust based ovsinit binary
This binary allows us to more reliably migrate IPs, which also
includes routes as well, with proper rollback in place to avoid
a system falling off the network.
This first iteration is used in the Neutron & OVN charts only,
but the long term plan is to leverage it into the Open vSwitch
charts potentially to have a single "auto_bridge_add" source
of truth.
Change-Id: Ic4de23297b67a602d9aba4b00f0fb234d9d37cfe
(cherry picked from commit 62c4dd963918ea193aea48be8db93f0ea52fa308)
diff --git a/crates/ovsinit/src/config.rs b/crates/ovsinit/src/config.rs
new file mode 100644
index 0000000..7c3d6b7
--- /dev/null
+++ b/crates/ovsinit/src/config.rs
@@ -0,0 +1,82 @@
+use serde::Deserialize;
+use std::collections::HashMap;
+use std::{fs::File, path::PathBuf};
+use thiserror::Error;
+use log::{error, info};
+
+#[derive(Deserialize)]
+pub struct NetworkConfig {
+ #[serde(flatten)]
+ pub bridges: HashMap<String, Option<String>>,
+}
+
+#[derive(Debug, Error)]
+pub enum NetworkConfigError {
+ #[error("Failed to open file: {0}")]
+ OpenFile(#[from] std::io::Error),
+
+ #[error("Failed to parse JSON: {0}")]
+ ParseJson(#[from] serde_json::Error),
+}
+
+impl NetworkConfig {
+ pub fn from_path(path: &PathBuf) -> Result<Self, NetworkConfigError> {
+ let file = File::open(path)?;
+ NetworkConfig::from_file(file)
+ }
+
+ pub fn from_file(file: File) -> Result<Self, NetworkConfigError> {
+ let config: NetworkConfig = serde_json::from_reader(file)?;
+ Ok(config)
+ }
+
+ pub fn bridges_with_interfaces_iter(&self) -> impl Iterator<Item = (&String, &String)> {
+ self.bridges.iter().filter_map(|(k, v)| {
+ if let Some(v) = v {
+ Some((k, v))
+ } else {
+ info!(bridge = k.as_str(); "Bridge has no interface, skipping.");
+
+ None
+ }
+ })
+ }
+
+ #[allow(dead_code)]
+ pub fn from_string(json: &str) -> Result<Self, NetworkConfigError> {
+ let config: NetworkConfig = serde_json::from_str(json)?;
+ Ok(config)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_null_interface() {
+ let config = NetworkConfig::from_string("{\"br-ex\": null}").unwrap();
+
+ assert_eq!(config.bridges.len(), 1);
+ assert_eq!(config.bridges.get("br-ex"), Some(&None));
+ }
+
+ #[test]
+ fn test_bridges_with_interfaces_iter_with_null_interface() {
+ let config = NetworkConfig::from_string("{\"br-ex\": null}").unwrap();
+
+ let mut iter = config.bridges_with_interfaces_iter();
+ assert_eq!(iter.next(), None);
+ }
+
+ #[test]
+ fn test_bridges_with_interfaces_iter_with_interface() {
+ let config = NetworkConfig::from_string("{\"br-ex\": \"bond0\"}").unwrap();
+
+ let mut iter = config.bridges_with_interfaces_iter();
+ assert_eq!(
+ iter.next(),
+ Some((&"br-ex".to_string(), &"bond0".to_string()))
+ );
+ }
+}