From 2edd24753a76bd11a8b03c1bea50dc3b1c794257 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Wed, 15 Oct 2025 12:13:09 -0400 Subject: [PATCH] implement port channel functions for Brocade::NetworkOperatingSystem --- brocade/examples/main.rs | 49 ++++++------ brocade/src/fast_iron.rs | 4 +- brocade/src/network_operating_system.rs | 100 ++++++++++++++++++++++-- 3 files changed, 119 insertions(+), 34 deletions(-) diff --git a/brocade/examples/main.rs b/brocade/examples/main.rs index 58f6c59..34dec21 100644 --- a/brocade/examples/main.rs +++ b/brocade/examples/main.rs @@ -2,6 +2,7 @@ use std::net::{IpAddr, Ipv4Addr}; use brocade::BrocadeOptions; use harmony_secret::{Secret, SecretManager}; +use harmony_types::switch::PortLocation; use serde::{Deserialize, Serialize}; #[derive(Secret, Clone, Debug, Serialize, Deserialize)] @@ -42,28 +43,28 @@ async fn main() { let entries = brocade.get_interfaces().await.unwrap(); println!("Interfaces: {entries:#?}"); - // 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(); + let version = brocade.version().await.unwrap(); + println!("Version: {version:?}"); + + println!("--------------"); + let mac_adddresses = brocade.get_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 = "1"; + 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(2, 0, 35)]; + 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 d33bd93..0a17661 100644 --- a/brocade/src/fast_iron.rs +++ b/brocade/src/fast_iron.rs @@ -76,8 +76,8 @@ impl FastIronClient { fn build_port_channel_commands( &self, + channel_id: PortChannelId, channel_name: &str, - channel_id: u8, ports: &[PortLocation], ) -> Vec { let mut commands = vec![ @@ -184,7 +184,7 @@ impl BrocadeClient for FastIronClient { "[Brocade] Configuring port-channel '{channel_name} {channel_id}' with ports: {ports:?}" ); - let commands = self.build_port_channel_commands(channel_name, channel_id, ports); + let commands = self.build_port_channel_commands(channel_id, channel_name, ports); self.shell .run_commands(commands, ExecutionMode::Privileged) .await?; diff --git a/brocade/src/network_operating_system.rs b/brocade/src/network_operating_system.rs index 6a3c8fb..4b9b271 100644 --- a/brocade/src/network_operating_system.rs +++ b/brocade/src/network_operating_system.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use async_trait::async_trait; use harmony_types::switch::{PortDeclaration, PortLocation}; -use log::debug; +use log::{debug, info}; use crate::{ BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, @@ -163,6 +163,8 @@ impl BrocadeClient for NetworkOperatingSystemClient { &self, interfaces: Vec<(String, PortOperatingMode)>, ) -> Result<(), Error> { + info!("[Brocade] Configuring {} interface(s)...", interfaces.len()); + let mut commands = vec!["configure terminal".to_string()]; for interface in interfaces { @@ -200,23 +202,105 @@ impl BrocadeClient for NetworkOperatingSystemClient { .run_commands(commands, ExecutionMode::Regular) .await?; + info!("[Brocade] Interfaces configured."); + Ok(()) } async fn find_available_channel_id(&self) -> Result { - todo!() + info!("[Brocade] Finding next available channel id..."); + + let output = self + .shell + .run_command("show port-channel", ExecutionMode::Regular) + .await?; + + let used_ids: Vec = output + .lines() + .skip(6) + .filter_map(|line| { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 8 { + return None; + } + + u8::from_str(parts[0]).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], + channel_id: PortChannelId, + channel_name: &str, + ports: &[PortLocation], ) -> Result<(), Error> { - todo!() + info!( + "[Brocade] Configuring port-channel '{channel_name} {channel_id}' with ports: {ports:?}" + ); + + let interfaces = self.get_interfaces().await?; + + let mut commands = vec![ + "configure terminal".into(), + format!("interface port-channel {}", channel_id), + "no shutdown".into(), + "exit".into(), + ]; + + for port in ports { + let interface = interfaces.iter().find(|i| i.port_location == *port); + let Some(interface) = interface else { + continue; + }; + + commands.push(format!("interface {}", interface.name)); + commands.push("no switchport".into()); + commands.push("no ip address".into()); + commands.push("no fabric isl enable".into()); + commands.push("no fabric trunk enable".into()); + commands.push(format!("channel-group {} mode active", channel_id)); + commands.push("no shutdown".into()); + commands.push("exit".into()); + } + + commands.push("write memory".into()); + + self.shell + .run_commands(commands, ExecutionMode::Regular) + .await?; + + info!("[Brocade] Port-channel '{channel_name}' configured."); + + Ok(()) } - async fn clear_port_channel(&self, _channel_name: &str) -> Result<(), Error> { - todo!() + async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> { + info!("[Brocade] Clearing port-channel: {channel_name}"); + + let commands = vec![ + "configure terminal".into(), + format!("no interface port-channel {}", channel_name), + "exit".into(), + "write memory".into(), + ]; + + self.shell + .run_commands(commands, ExecutionMode::Regular) + .await?; + + info!("[Brocade] Port-channel '{channel_name}' cleared."); + Ok(()) } }