From 46d27ce2276b528ebcf1cc87b12572941251bfdc Mon Sep 17 00:00:00 2001 From: Hunter Wigelsworth Date: Tue, 19 Nov 2024 00:01:18 -0500 Subject: [PATCH] fix doc tests and add vni/vlan helpers --- .gitignore | 1 + src/lib.rs | 45 ++++++++------- src/network_interfaces.rs | 115 +++++++++++++++++++++++++++++++++++++- src/parser.rs | 13 +++-- 4 files changed, 145 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..334c109 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/tests \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 093bfdc..c5cd60b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,25 +25,23 @@ //! use interface_rs::NetworkInterfaces; //! use interface_rs::interface::{Interface, Family}; //! -//! fn main() -> std::io::Result<()> { -//! // Load the interfaces from a file -//! let mut net_ifaces = NetworkInterfaces::load("/path/to/interfaces")?; +//! fn main() -> Result<(), Box> { +//! // Load interfaces (replace with an existing file path during tests) +//! let mut net_ifaces = NetworkInterfaces::load("tests/interfaces")?; //! -//! // Retrieve and modify an existing interface using the builder pattern +//! // Retrieve and modify an existing interface //! if let Some(iface) = net_ifaces.get_interface("eth0") { //! let modified_iface = iface.edit() //! .with_method("static") +//! .remove_option("address") //! .with_option("address", "192.168.1.50") //! .with_option("netmask", "255.255.255.0") //! .build(); -//! -//! // Replace the existing interface with the modified one //! net_ifaces.add_interface(modified_iface); //! } //! -//! // Save changes back to the file +//! // Save changes //! net_ifaces.save()?; -//! //! Ok(()) //! } //! ``` @@ -54,21 +52,21 @@ //! use interface_rs::NetworkInterfaces; //! use interface_rs::interface::{Interface, Family}; //! -//! fn main() -> std::io::Result<()> { -//! let mut net_ifaces = NetworkInterfaces::load("/path/to/interfaces")?; +//! fn main() -> Result<(), Box> { +//! // Load interfaces (replace with an existing file path during tests) +//! let mut net_ifaces = NetworkInterfaces::load("tests/interfaces")?; //! //! // Create a new interface using the builder pattern -//! let new_iface = Interface::builder("swp1") -//! .with_auto(true) -//! .with_allow("hotplug") -//! .with_family(Family::Inet) -//! .with_method("static") -//! .with_option("address", "192.168.100.1") -//! .with_option("netmask", "255.255.255.0") -//! .build(); -//! -//! // Add the new interface to the collection -//! net_ifaces.add_interface(new_iface); +//! net_ifaces.add_interface( +//! Interface::builder("swp1") +//! .with_auto(true) +//! .with_allow("hotplug") +//! .with_family(Family::Inet) +//! .with_method("static") +//! .with_option("address", "192.168.100.1") +//! .with_option("netmask", "255.255.255.0") +//! .build() +//! ); //! //! // Save changes back to the file //! net_ifaces.save()?; @@ -82,8 +80,9 @@ //! ```rust //! use interface_rs::NetworkInterfaces; //! -//! fn main() -> std::io::Result<()> { -//! let mut net_ifaces = NetworkInterfaces::load("/path/to/interfaces")?; +//! fn main() -> Result<(), Box> { +//! // Load interfaces (replace with an existing file path during tests) +//! let mut net_ifaces = NetworkInterfaces::load("tests/interfaces")?; //! //! // Delete an interface by name //! net_ifaces.delete_interface("eth0"); diff --git a/src/network_interfaces.rs b/src/network_interfaces.rs index 81119c3..7c2931a 100644 --- a/src/network_interfaces.rs +++ b/src/network_interfaces.rs @@ -21,7 +21,7 @@ use std::time::SystemTime; /// use interface_rs::NetworkInterfaces; /// use interface_rs::interface::Interface; /// -/// let mut net_ifaces = NetworkInterfaces::load("/path/to/interfaces").unwrap(); +/// let mut net_ifaces = NetworkInterfaces::load("tests/interfaces").unwrap(); /// /// // Modify an interface /// if let Some(iface) = net_ifaces.get_interface_mut("eth0") { @@ -149,6 +149,55 @@ impl NetworkInterfaces { self.interfaces.is_empty() } + /// Finds the next unused VLAN ID within a specified range. + /// + /// # Arguments + /// + /// * `start` - The starting VLAN ID (inclusive). + /// * `end` - The ending VLAN ID (inclusive). + /// + /// # Returns + /// + /// * `Option` - The next unused VLAN ID, or `None` if all are used. + pub fn next_unused_vlan_in_range(&self, start: u16, end: u16) -> Option { + for vlan_id in start..=end { + let vlan_name = format!("vlan{}", vlan_id); + if !self.interfaces.contains_key(&vlan_name) { + return Some(vlan_id); + } + } + None // All VLAN IDs in the specified range are used + } + + /// Retrieves the VLAN associated with an existing VNI interface. + /// + /// # Arguments + /// + /// * `vni_id` - The VNI ID to search for (e.g., `1347682`). + /// + /// # Returns + /// + /// * `Option` - The VLAN ID specified in the `bridge-access` option, or `None` if + /// the interface does not exist or the option is not present. + pub fn get_existing_vni_vlan(&self, vni_id: u32) -> Option { + let vni_name = format!("vni{}", vni_id); + + // Check if the interface exists + let interface = self.interfaces.get(&vni_name)?; + + // Look for the `bridge-access` option + for (key, value) in &interface.options { + if key == "bridge-access" { + // Try to parse the value as a u16 + if let Ok(vlan_id) = value.parse::() { + return Some(vlan_id); + } + } + } + + None // No `bridge-access` option or invalid value + } + /// Saves changes back to the `interfaces(5)` file. /// /// # Errors @@ -257,3 +306,67 @@ impl NetworkInterfaces { self.interfaces.iter() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_next_unused_vlan_in_range() { + // Create a `NetworkInterfaces` instance with some used VLANs + let mut network_interfaces = NetworkInterfaces { + interfaces: HashMap::new(), + path: None, + last_modified: None, + comments: Vec::new(), + sources: Vec::new(), + }; + + // Add some VLAN interfaces to simulate used IDs + network_interfaces.add_interface(Interface::builder("vlan1000").build()); + network_interfaces.add_interface(Interface::builder("vlan1001").build()); + network_interfaces.add_interface(Interface::builder("vlan1003").build()); + + // Test: Find the next unused VLAN ID in the range 1000 to 1999 + let next_vlan_id = network_interfaces.next_unused_vlan_in_range(1000, 1999); + assert_eq!(next_vlan_id, Some(1002)); + + // Test: Find the next unused VLAN ID when all VLANs are used in the range + network_interfaces.add_interface(Interface::builder("vlan1002").build()); + let next_vlan_id = network_interfaces.next_unused_vlan_in_range(1000, 1003); + assert_eq!(next_vlan_id, None); + + // Test: Find the next unused VLAN ID in a different range + let next_vlan_id = network_interfaces.next_unused_vlan_in_range(2000, 2005); + assert_eq!(next_vlan_id, Some(2000)); + } + + #[test] + fn test_get_existing_vni_vlan() { + let mut network_interfaces = NetworkInterfaces { + interfaces: HashMap::new(), + path: None, + last_modified: None, + comments: Vec::new(), + sources: Vec::new(), + }; + + // Add a VNI interface + network_interfaces.add_interface( + Interface::builder("vni123456") + .with_auto(true) + .with_option("bridge-access", "1002") + .build(), + ); + + // Test: Find VLAN ID for existing VNI + assert_eq!(network_interfaces.get_existing_vni_vlan(123456), Some(1002)); + + // Test: No `bridge-access` option + network_interfaces.add_interface(Interface::builder("vni987654").with_auto(true).build()); + assert_eq!(network_interfaces.get_existing_vni_vlan(987654), None); + + // Test: Nonexistent VNI + assert_eq!(network_interfaces.get_existing_vni_vlan(666), None); + } +} diff --git a/src/parser.rs b/src/parser.rs index b736d63..fbf4af4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -126,15 +126,18 @@ impl Parser { // Parse family let family = match tokens.get(2) { - Some(s) => Some(s.parse::().map_err(|e| ParserError { - message: e.to_string(), - line: Some(line_number + 1), - })?), + Some(s) => s.parse::().ok(), None => None, }; // Parse method - let method = tokens.get(3).map(|s| s.to_string()); + let method = match tokens.len() { + // If family is valid, method is the next token + 4 if family.is_some() => Some(tokens[3].to_string()), + // If family is absent, interpret the third token as the method + 3 if family.is_none() => Some(tokens[2].to_string()), + _ => None, + }; if let Some(family) = family { builder = builder.with_family(family);