diff --git a/brocade/examples/main.rs b/brocade/examples/main.rs index d13cf50..fac97ee 100644 --- a/brocade/examples/main.rs +++ b/brocade/examples/main.rs @@ -7,8 +7,9 @@ use harmony_types::switch::PortLocation; async fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - let ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 250)); // old brocade @ ianlet + // let ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 250)); // old brocade @ ianlet // let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 55, 101)); // brocade @ sto1 + let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st let switch_addresses = vec![ip]; let brocade = brocade::init( @@ -24,28 +25,34 @@ async fn main() { .await .expect("Brocade client failed to connect"); - let version = brocade.version().await.unwrap(); - println!("Version: {version:?}"); + let entries = brocade.get_stack_topology().await.unwrap(); + println!("Stack topology: {entries:#?}"); - println!("--------------"); - let mac_adddresses = brocade.show_mac_address_table().await.unwrap(); - println!("VLAN\tMAC\t\t\tPORT"); - for mac in mac_adddresses { - println!("{}\t{}\t{}", mac.vlan, mac.mac_address, mac.port); - } + let entries = brocade.get_interfaces().await.unwrap(); + println!("Interfaces: {entries:#?}"); - println!("--------------"); - let channel_name = "HARMONY_LAG"; - brocade.clear_port_channel(channel_name).await.unwrap(); - - println!("--------------"); - let channel_id = brocade.find_available_channel_id().await.unwrap(); - - println!("--------------"); - let channel_name = "HARMONY_LAG"; - let ports = [PortLocation(1, 1, 3), PortLocation(1, 1, 4)]; - brocade - .create_port_channel(channel_id, channel_name, &ports) - .await - .unwrap(); + // let version = brocade.version().await.unwrap(); + // println!("Version: {version:?}"); + // + // println!("--------------"); + // let mac_adddresses = brocade.show_mac_address_table().await.unwrap(); + // println!("VLAN\tMAC\t\t\tPORT"); + // for mac in mac_adddresses { + // println!("{}\t{}\t{}", mac.vlan, mac.mac_address, mac.port); + // } + // + // println!("--------------"); + // let channel_name = "HARMONY_LAG"; + // brocade.clear_port_channel(channel_name).await.unwrap(); + // + // println!("--------------"); + // let channel_id = brocade.find_available_channel_id().await.unwrap(); + // + // println!("--------------"); + // let channel_name = "HARMONY_LAG"; + // let ports = [PortLocation(1, 1, 3), PortLocation(1, 1, 4)]; + // brocade + // .create_port_channel(channel_id, channel_name, &ports) + // .await + // .unwrap(); } diff --git a/brocade/src/fast_iron.rs b/brocade/src/fast_iron.rs index 258b326..9cb57e4 100644 --- a/brocade/src/fast_iron.rs +++ b/brocade/src/fast_iron.rs @@ -1,7 +1,7 @@ use super::BrocadeClient; use crate::{ - BrocadeInfo, Error, ExecutionMode, MacAddressEntry, PortChannelId, parse_brocade_mac_address, - shell::BrocadeShell, + BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry, + PortChannelId, parse_brocade_mac_address, shell::BrocadeShell, }; use async_trait::async_trait; @@ -11,12 +11,22 @@ use regex::Regex; use std::{collections::HashSet, str::FromStr}; pub struct FastIronClient { - pub shell: BrocadeShell, - pub version: BrocadeInfo, + shell: BrocadeShell, + version: BrocadeInfo, } impl FastIronClient { - pub fn parse_mac_entry(&self, line: &str) -> Option> { + pub fn init(mut shell: BrocadeShell, version_info: BrocadeInfo) -> Self { + shell.before_all(vec!["skip-page-display".into()]); + shell.after_all(vec!["page".into()]); + + Self { + shell, + version: version_info, + } + } + + fn parse_mac_entry(&self, line: &str) -> Option> { debug!("[Brocade] Parsing mac address entry: {line}"); let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 3 { @@ -49,7 +59,22 @@ impl FastIronClient { } } - pub fn build_port_channel_commands( + fn parse_stack_port_entry(&self, line: &str) -> Option> { + debug!("[Brocade] Parsing stack port entry: {line}"); + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 10 { + return None; + } + + let local_port = PortLocation::from_str(parts[0]).ok()?; + + Some(Ok(InterSwitchLink { + local_port, + remote_port: None, // FIXME: Map the remote port as well + })) + } + + fn build_port_channel_commands( &self, channel_name: &str, channel_id: u8, @@ -80,7 +105,12 @@ impl BrocadeClient for FastIronClient { Ok(self.version.clone()) } - async fn show_mac_address_table(&self) -> Result, Error> { + async fn setup(&self) -> Result<(), Error> { + // Nothing to do, for now + Ok(()) + } + + async fn get_mac_address_table(&self) -> Result, Error> { info!("[Brocade] Showing MAC address table..."); let output = self @@ -95,6 +125,23 @@ impl BrocadeClient for FastIronClient { .collect() } + async fn get_stack_topology(&self) -> Result, Error> { + let output = self + .shell + .run_command("show interface stack-ports", crate::ExecutionMode::Regular) + .await?; + + output + .lines() + .skip(1) + .filter_map(|line| self.parse_stack_port_entry(line)) + .collect() + } + + async fn get_interfaces(&self) -> Result, Error> { + todo!() + } + async fn find_available_channel_id(&self) -> Result { info!("[Brocade] Finding next available channel id..."); diff --git a/brocade/src/lib.rs b/brocade/src/lib.rs index 0517292..9311df0 100644 --- a/brocade/src/lib.rs +++ b/brocade/src/lib.rs @@ -73,6 +73,70 @@ pub struct MacAddressEntry { pub type PortChannelId = u8; +/// Represents a single physical or logical link connecting two switches within a stack or fabric. +/// +/// This structure provides a standardized view of the topology regardless of the +/// underlying Brocade OS configuration (stacking vs. fabric). +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct InterSwitchLink { + /// The local port on the switch where the topology command was run. + pub local_port: PortLocation, + /// The port on the directly connected neighboring switch. + pub remote_port: Option, +} + +/// Represents the key running configuration status of a single switch interface. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct InterfaceInfo { + /// The full configuration name (e.g., "TenGigabitEthernet 1/0/1", "FortyGigabitEthernet 2/0/2"). + pub name: String, + /// The physical location of the interface. + pub port_location: PortLocation, + /// The parsed type and name prefix of the interface. + pub interface_type: InterfaceType, + /// The primary configuration mode defining the interface's behavior (L2, L3, Fabric). + pub operating_mode: Option, + /// Indicates the current state of the interface. + pub status: InterfaceStatus, +} + +/// Categorizes the functional type of a switch interface. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum InterfaceType { + /// Physical or virtual Ethernet interface (e.g., TenGigabitEthernet, FortyGigabitEthernet). + Ethernet(String), +} + +impl fmt::Display for InterfaceType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InterfaceType::Ethernet(name) => write!(f, "{name}"), + } + } +} + +/// Defines the primary configuration mode of a switch interface, representing mutually exclusive roles. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PortOperatingMode { + /// The interface is explicitly configured for Brocade fabric roles (ISL or Trunk enabled). + Fabric, + /// The interface is configured for standard Layer 2 switching as Trunk port (`switchport mode trunk`). + Trunk, + /// The interface is configured for standard Layer 2 switching as Access port (`switchport` without trunk mode). + Access, +} + +/// Defines the possible status of an interface. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum InterfaceStatus { + /// The interface is connected. + Connected, + /// The interface is not connected and is not expected to be. + NotConnected, + /// The interface is not connected but is expected to be (configured with `no shutdown`). + SfpAbsent, +} + pub async fn init( ip_addresses: &[IpAddr], port: u16, @@ -89,26 +153,87 @@ pub async fn init( .await?; Ok(match version_info.os { - BrocadeOs::FastIron => Box::new(FastIronClient { - shell, - version: version_info, - }), - BrocadeOs::NetworkOperatingSystem => Box::new(NetworkOperatingSystemClient { - shell, - version: version_info, - }), + BrocadeOs::FastIron => Box::new(FastIronClient::init(shell, version_info)), + BrocadeOs::NetworkOperatingSystem => { + Box::new(NetworkOperatingSystemClient::init(shell, version_info)) + } BrocadeOs::Unknown => todo!(), }) } #[async_trait] pub trait BrocadeClient { + /// Retrieves the operating system and version details from the connected Brocade switch. + /// + /// This is typically the first call made after establishing a connection to determine + /// the switch OS family (e.g., FastIron, NOS) for feature compatibility. + /// + /// # Returns + /// + /// A `BrocadeInfo` structure containing parsed OS type and version string. async fn version(&self) -> Result; - async fn show_mac_address_table(&self) -> Result, Error>; + /// Executes essential, idempotent, one-time initial configuration steps. + /// + /// This is an opiniated procedure that setups a switch to provide high availability + /// capabilities as decided by the NationTech team. + /// + /// This includes tasks like ensuring necessary feature licenses are active, + /// enabling switchport for all interfaces except the ones intended for Fabric Networking, etc. + /// + /// The implementation must ensure the operation is **idempotent** (safe to run multiple times) + /// and that it doesn't break existing configurations. + async fn setup(&self) -> Result<(), Error>; + /// Retrieves the dynamically learned MAC address table from the switch. + /// + /// This is crucial for discovering where specific network endpoints (MAC addresses) + /// are currently located on the physical ports. + /// + /// # Returns + /// + /// A vector of `MacAddressEntry`, where each entry typically contains VLAN, MAC address, + /// and the associated port name/index. + async fn get_mac_address_table(&self) -> Result, Error>; + + /// Derives the physical connections used to link multiple switches together + /// to form a single logical entity (stack, fabric, etc.). + /// + /// This abstracts the underlying configuration (e.g., stack ports, fabric ports) + /// to return a standardized view of the topology. + /// + /// # Returns + /// + /// A vector of `InterSwitchLink` structs detailing which ports are used for stacking/fabric. + /// If the switch is not stacked, returns an empty vector. + async fn get_stack_topology(&self) -> Result, Error>; + + /// Retrieves the status for all interfaces + /// + /// # Returns + /// + /// A vector of `InterfaceInfo` structures. + async fn get_interfaces(&self) -> Result, Error>; + + /// Scans the existing configuration to find the next available (unused) + /// Port-Channel ID (`lag` or `trunk`) for assignment. + /// + /// # Returns + /// + /// The smallest, unassigned `PortChannelId` within the supported range. async fn find_available_channel_id(&self) -> Result; + /// Creates and configures a new Port-Channel (Link Aggregation Group or LAG) + /// using the specified channel ID and ports. + /// + /// The resulting configuration must be persistent (saved to startup-config). + /// Assumes a static LAG configuration mode unless specified otherwise by the implementation. + /// + /// # Parameters + /// + /// * `channel_id`: The ID (e.g., 1-128) for the logical port channel. + /// * `channel_name`: A descriptive name for the LAG (used in configuration context). + /// * `ports`: A slice of `PortLocation` structs defining the physical member ports. async fn create_port_channel( &self, channel_id: PortChannelId, @@ -116,6 +241,15 @@ pub trait BrocadeClient { ports: &[PortLocation], ) -> Result<(), Error>; + /// Removes all configuration associated with the specified Port-Channel name. + /// + /// This operation should be idempotent; attempting to clear a non-existent + /// channel should succeed (or return a benign error). + /// + /// # Parameters + /// + /// * `channel_name`: The name of the Port-Channel (LAG) to delete. + /// async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error>; } diff --git a/brocade/src/network_operating_system.rs b/brocade/src/network_operating_system.rs index a0a90e6..883bba9 100644 --- a/brocade/src/network_operating_system.rs +++ b/brocade/src/network_operating_system.rs @@ -5,17 +5,27 @@ use harmony_types::switch::{PortDeclaration, PortLocation}; use log::debug; use crate::{ - BrocadeClient, BrocadeInfo, Error, MacAddressEntry, PortChannelId, parse_brocade_mac_address, + BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceInfo, InterfaceStatus, + InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode, parse_brocade_mac_address, shell::BrocadeShell, }; pub struct NetworkOperatingSystemClient { - pub shell: BrocadeShell, - pub version: BrocadeInfo, + shell: BrocadeShell, + version: BrocadeInfo, } impl NetworkOperatingSystemClient { - pub fn parse_mac_entry(&self, line: &str) -> Option> { + pub fn init(mut shell: BrocadeShell, version_info: BrocadeInfo) -> Self { + shell.before_all(vec!["terminal length 0".into()]); + + Self { + shell, + version: version_info, + } + } + + fn parse_mac_entry(&self, line: &str) -> Option> { debug!("[Brocade] Parsing mac address entry: {line}"); let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 5 { @@ -47,6 +57,58 @@ impl NetworkOperatingSystemClient { Err(e) => Some(Err(e)), } } + + fn parse_inter_switch_link_entry(&self, line: &str) -> Option> { + debug!("[Brocade] Parsing inter switch link entry: {line}"); + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 10 { + return None; + } + + let local_port = PortLocation::from_str(parts[2]).ok()?; + let remote_port = PortLocation::from_str(parts[5]).ok()?; + + Some(Ok(InterSwitchLink { + local_port, + remote_port: Some(remote_port), + })) + } + + fn parse_interface_status_entry(&self, line: &str) -> Option> { + debug!("[Brocade] Parsing interface status entry: {line}"); + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 6 { + return None; + } + + let interface_type = match parts[0] { + "Fo" => InterfaceType::Ethernet("FortyGigabitEthernet".to_string()), + "Te" => InterfaceType::Ethernet("TenGigabitEthernet".to_string()), + _ => return None, + }; + let port_location = PortLocation::from_str(parts[1]).ok()?; + let status = match parts[2] { + "connected" => InterfaceStatus::Connected, + "notconnected" => InterfaceStatus::NotConnected, + "sfpAbsent" => InterfaceStatus::SfpAbsent, + _ => return None, + }; + let operating_mode = match parts[3] { + "ISL" => Some(PortOperatingMode::Fabric), + "Trunk" => Some(PortOperatingMode::Trunk), + "Access" => Some(PortOperatingMode::Access), + "--" => None, + _ => return None, + }; + + Some(Ok(InterfaceInfo { + name: format!("{} {}", interface_type, port_location), + port_location, + interface_type, + operating_mode, + status, + })) + } } #[async_trait] @@ -55,7 +117,14 @@ impl BrocadeClient for NetworkOperatingSystemClient { Ok(self.version.clone()) } - async fn show_mac_address_table(&self) -> Result, Error> { + async fn setup(&self) -> Result<(), Error> { + let links = self.get_stack_topology().await?; + let interfaces = self.get_interfaces().await?; + + todo!() + } + + async fn get_mac_address_table(&self) -> Result, Error> { let output = self .shell .run_command("show mac-address-table", crate::ExecutionMode::Regular) @@ -68,6 +137,35 @@ impl BrocadeClient for NetworkOperatingSystemClient { .collect() } + async fn get_stack_topology(&self) -> Result, Error> { + let output = self + .shell + .run_command("show fabric isl", crate::ExecutionMode::Regular) + .await?; + + output + .lines() + .skip(6) + .filter_map(|line| self.parse_inter_switch_link_entry(line)) + .collect() + } + + async fn get_interfaces(&self) -> Result, Error> { + let output = self + .shell + .run_command( + "show interface status rbridge-id all", + crate::ExecutionMode::Regular, + ) + .await?; + + output + .lines() + .skip(2) + .filter_map(|line| self.parse_interface_status_entry(line)) + .collect() + } + async fn find_available_channel_id(&self) -> Result { todo!() } diff --git a/brocade/src/shell.rs b/brocade/src/shell.rs index 8f9d1fc..cfa672d 100644 --- a/brocade/src/shell.rs +++ b/brocade/src/shell.rs @@ -14,11 +14,13 @@ use russh::ChannelMsg; use tokio::time::timeout; pub struct BrocadeShell { - pub ip: IpAddr, - pub port: u16, - pub username: String, - pub password: String, - pub options: BrocadeOptions, + ip: IpAddr, + port: u16, + username: String, + password: String, + options: BrocadeOptions, + before_all_commands: Vec, + after_all_commands: Vec, } impl BrocadeShell { @@ -41,6 +43,8 @@ impl BrocadeShell { port, username: username.to_string(), password: password.to_string(), + before_all_commands: vec![], + after_all_commands: vec![], options, }) } @@ -66,14 +70,22 @@ impl BrocadeShell { >, { let mut session = self.open_session(mode).await?; + + let _ = session.run_commands(self.before_all_commands.clone()).await; let result = callback(&mut session).await; + let _ = session.run_commands(self.after_all_commands.clone()).await; + session.close().await?; result } pub async fn run_command(&self, command: &str, mode: ExecutionMode) -> Result { let mut session = self.open_session(mode).await?; + + let _ = session.run_commands(self.before_all_commands.clone()).await; let result = session.run_command(command).await; + let _ = session.run_commands(self.after_all_commands.clone()).await; + session.close().await?; result } @@ -84,10 +96,22 @@ impl BrocadeShell { mode: ExecutionMode, ) -> Result<(), Error> { let mut session = self.open_session(mode).await?; + + let _ = session.run_commands(self.before_all_commands.clone()).await; let result = session.run_commands(commands).await; + let _ = session.run_commands(self.after_all_commands.clone()).await; + session.close().await?; result } + + pub fn before_all(&mut self, commands: Vec) { + self.before_all_commands = commands; + } + + pub fn after_all(&mut self, commands: Vec) { + self.after_all_commands = commands; + } } pub struct BrocadeSession { diff --git a/harmony/src/domain/topology/ha_cluster.rs b/harmony/src/domain/topology/ha_cluster.rs index 8c65e70..e0c7151 100644 --- a/harmony/src/domain/topology/ha_cluster.rs +++ b/harmony/src/domain/topology/ha_cluster.rs @@ -513,6 +513,12 @@ impl HttpServer for HAClusterTopology { #[async_trait] impl Switch for HAClusterTopology { + async fn setup_switch(&self) -> Result<(), SwitchError> { + let client = self.get_switch_client().await?; + client.setup().await?; + Ok(()) + } + async fn get_port_for_mac_address( &self, mac_address: &MacAddress, diff --git a/harmony/src/domain/topology/network.rs b/harmony/src/domain/topology/network.rs index 60417b0..e31ce23 100644 --- a/harmony/src/domain/topology/network.rs +++ b/harmony/src/domain/topology/network.rs @@ -178,6 +178,8 @@ impl FromStr for DnsRecordType { #[async_trait] pub trait Switch: Send + Sync { + async fn setup_switch(&self) -> Result<(), SwitchError>; + async fn get_port_for_mac_address( &self, mac_address: &MacAddress, @@ -224,6 +226,8 @@ impl Error for SwitchError {} #[async_trait] pub trait SwitchClient: Send + Sync { + async fn setup(&self) -> Result<(), SwitchError>; + async fn find_port( &self, mac_address: &MacAddress, diff --git a/harmony/src/infra/brocade.rs b/harmony/src/infra/brocade.rs index 3734527..7bbf8bc 100644 --- a/harmony/src/infra/brocade.rs +++ b/harmony/src/infra/brocade.rs @@ -27,13 +27,20 @@ impl BrocadeSwitchClient { #[async_trait] impl SwitchClient for BrocadeSwitchClient { + async fn setup(&self) -> Result<(), SwitchError> { + self.brocade + .setup() + .await + .map_err(|e| SwitchError::new(e.to_string())) + } + async fn find_port( &self, mac_address: &MacAddress, ) -> Result, SwitchError> { let table = self .brocade - .show_mac_address_table() + .get_mac_address_table() .await .map_err(|e| SwitchError::new(format!("{e}")))?; diff --git a/harmony/src/modules/okd/host_network.rs b/harmony/src/modules/okd/host_network.rs index 58a5e98..8e89e4f 100644 --- a/harmony/src/modules/okd/host_network.rs +++ b/harmony/src/modules/okd/host_network.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use harmony_types::id::Id; -use log::{debug, info, warn}; +use log::{debug, info}; use serde::Serialize; use crate::{ @@ -34,6 +34,58 @@ pub struct HostNetworkConfigurationInterpret { score: HostNetworkConfigurationScore, } +impl HostNetworkConfigurationInterpret { + async fn configure_network_for_host( + &self, + topology: &T, + host: &PhysicalHost, + ) -> Result<(), InterpretError> { + let switch_ports = self.collect_switch_ports_for_host(topology, host).await?; + if !switch_ports.is_empty() { + topology + .configure_host_network(host, HostNetworkConfig { switch_ports }) + .await + .map_err(|e| InterpretError::new(format!("Failed to configure host: {e}")))?; + } + + Ok(()) + } + async fn collect_switch_ports_for_host( + &self, + topology: &T, + host: &PhysicalHost, + ) -> Result, InterpretError> { + let mut switch_ports = vec![]; + + for network_interface in &host.network { + let mac_address = network_interface.mac_address; + + match topology.get_port_for_mac_address(&mac_address).await { + Ok(Some(port)) => { + switch_ports.push(SwitchPort { + interface: NetworkInterface { + name: network_interface.name.clone(), + mac_address, + speed_mbps: network_interface.speed_mbps, + mtu: network_interface.mtu, + }, + port, + }); + } + Ok(None) => debug!("No port found for host '{}', skipping", host.id), + Err(e) => { + return Err(InterpretError::new(format!( + "Failed to get port for host '{}': {}", + host.id, e + ))); + } + } + } + + Ok(switch_ports) + } +} + #[async_trait] impl Interpret for HostNetworkConfigurationInterpret { fn get_name(&self) -> InterpretName { @@ -57,43 +109,25 @@ impl Interpret for HostNetworkConfigurationInterpret { _inventory: &Inventory, topology: &T, ) -> Result { + if self.score.hosts.is_empty() { + return Ok(Outcome::noop("No hosts to configure".into())); + } + info!( "Started network configuration for {} host(s)...", self.score.hosts.len() ); + topology + .setup_switch() + .await + .map_err(|e| InterpretError::new(format!("Switch setup failed: {e}")))?; + let mut configured_host_count = 0; - for host in &self.score.hosts { - let mut switch_ports = vec![]; - - for network_interface in &host.network { - let mac_address = network_interface.mac_address; - - match topology.get_port_for_mac_address(&mac_address).await { - Ok(Some(port)) => { - switch_ports.push(SwitchPort { - interface: NetworkInterface { - name: network_interface.name.clone(), - mac_address, - speed_mbps: network_interface.speed_mbps, - mtu: network_interface.mtu, - }, - port, - }); - } - Ok(None) => debug!("No port found for host '{}', skipping", host.id), - Err(e) => warn!("Failed to get port for host '{}': {}", host.id, e), - } - } - - if !switch_ports.is_empty() { - configured_host_count += 1; - topology - .configure_host_network(host, HostNetworkConfig { switch_ports }) - .await - .map_err(|e| InterpretError::new(format!("Failed to configure host: {e}")))?; - } + // FIXME: Clear the previous config for host + self.configure_network_for_host(topology, host).await?; + configured_host_count += 1; } if configured_host_count > 0 { @@ -151,6 +185,18 @@ mod tests { pub static ref ANOTHER_PORT: PortLocation = PortLocation(2, 0, 42); } + #[tokio::test] + async fn should_setup_switch() { + let host = given_host(&HOST_ID, vec![EXISTING_INTERFACE.clone()]); + let score = given_score(vec![host]); + let topology = TopologyWithSwitch::new(); + + let _ = score.interpret(&Inventory::empty(), &topology).await; + + let switch_setup = topology.switch_setup.lock().unwrap(); + assert_that!(*switch_setup).is_true(); + } + #[tokio::test] async fn host_with_one_mac_address_should_create_bond_with_one_interface() { let host = given_host(&HOST_ID, vec![EXISTING_INTERFACE.clone()]); @@ -284,6 +330,7 @@ mod tests { struct TopologyWithSwitch { available_ports: Arc>>, configured_host_networks: Arc>>, + switch_setup: Arc>, } impl TopologyWithSwitch { @@ -291,6 +338,7 @@ mod tests { Self { available_ports: Arc::new(Mutex::new(vec![PORT.clone(), ANOTHER_PORT.clone()])), configured_host_networks: Arc::new(Mutex::new(vec![])), + switch_setup: Arc::new(Mutex::new(false)), } } @@ -298,6 +346,7 @@ mod tests { Self { available_ports: Arc::new(Mutex::new(vec![])), configured_host_networks: Arc::new(Mutex::new(vec![])), + switch_setup: Arc::new(Mutex::new(false)), } } } @@ -315,6 +364,12 @@ mod tests { #[async_trait] impl Switch for TopologyWithSwitch { + async fn setup_switch(&self) -> Result<(), SwitchError> { + let mut switch_configured = self.switch_setup.lock().unwrap(); + *switch_configured = true; + Ok(()) + } + async fn get_port_for_mac_address( &self, _mac_address: &MacAddress,