use super::BrocadeClient; use crate::{ BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry, PortChannelId, PortOperatingMode, parse_brocade_mac_address, shell::BrocadeShell, }; use async_trait::async_trait; use harmony_types::switch::{PortDeclaration, PortLocation}; use log::{debug, info}; use regex::Regex; use std::{collections::HashSet, str::FromStr}; #[derive(Debug)] pub struct FastIronClient { shell: BrocadeShell, version: BrocadeInfo, } impl FastIronClient { 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 { return None; } let (vlan, mac_address, port) = match parts.len() { 3 => ( u16::from_str(parts[0]).ok()?, parse_brocade_mac_address(parts[1]).ok()?, parts[2].to_string(), ), _ => ( 1, parse_brocade_mac_address(parts[0]).ok()?, parts[1].to_string(), ), }; let port = PortDeclaration::parse(&port).map_err(|e| Error::UnexpectedError(format!("{e}"))); match port { Ok(p) => Some(Ok(MacAddressEntry { vlan, mac_address, port: p, })), Err(e) => Some(Err(e)), } } 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, })) } fn build_port_channel_commands( &self, channel_id: PortChannelId, channel_name: &str, ports: &[PortLocation], ) -> Vec { let mut commands = vec![ "configure terminal".to_string(), format!("lag {channel_name} static id {channel_id}"), ]; for port in ports { commands.push(format!("ports ethernet {port}")); } commands.push(format!("primary-port {}", ports[0])); commands.push("deploy".into()); commands.push("exit".into()); commands.push("write memory".into()); commands.push("exit".into()); commands } } #[async_trait] impl BrocadeClient for FastIronClient { async fn version(&self) -> Result { Ok(self.version.clone()) } async fn get_mac_address_table(&self) -> Result, Error> { info!("[Brocade] Showing MAC address table..."); let output = self .shell .run_command("show mac-address", ExecutionMode::Regular) .await?; output .lines() .skip(2) .filter_map(|line| self.parse_mac_entry(line)) .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 configure_interfaces( &self, _interfaces: Vec<(String, PortOperatingMode)>, ) -> Result<(), Error> { todo!() } async fn find_available_channel_id(&self) -> Result { info!("[Brocade] Finding next available channel id..."); let output = self .shell .run_command("show lag", ExecutionMode::Regular) .await?; let re = Regex::new(r"=== LAG .* ID\s+(\d+)").expect("Invalid regex"); let used_ids: HashSet = output .lines() .filter_map(|line| { re.captures(line) .and_then(|c| c.get(1)) .and_then(|id_match| id_match.as_str().parse().ok()) }) .collect(); let mut next_id: u8 = 1; loop { if !used_ids.contains(&next_id) { break; } next_id += 1; } info!("[Brocade] Found channel id: {next_id}"); Ok(next_id) } async fn create_port_channel( &self, channel_id: PortChannelId, channel_name: &str, ports: &[PortLocation], ) -> Result<(), Error> { info!( "[Brocade] Configuring port-channel '{channel_name} {channel_id}' with ports: {ports:?}" ); let commands = self.build_port_channel_commands(channel_id, channel_name, ports); self.shell .run_commands(commands, ExecutionMode::Privileged) .await?; info!("[Brocade] Port-channel '{channel_name}' configured."); Ok(()) } async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> { info!("[Brocade] Clearing port-channel: {channel_name}"); let commands = vec![ "configure terminal".to_string(), format!("no lag {channel_name}"), "write memory".to_string(), ]; self.shell .run_commands(commands, ExecutionMode::Privileged) .await?; info!("[Brocade] Port-channel '{channel_name}' cleared."); Ok(()) } }