implement switch brocade setup by configuring interfaces with operating mode access
All checks were successful
Run Check Script / check (pull_request) Successful in 1m6s

This commit is contained in:
Ian Letourneau
2025-10-14 17:21:13 -04:00
parent 1265cebfa7
commit da5be17cb6
9 changed files with 401 additions and 44 deletions

View File

@@ -14,3 +14,5 @@ tokio.workspace = true
log.workspace = true
env_logger.workspace = true
regex = "1.11.3"
harmony_secret = { path = "../harmony_secret" }
serde.workspace = true

View File

@@ -1,22 +1,33 @@
use std::net::{IpAddr, Ipv4Addr};
use brocade::BrocadeOptions;
use harmony_types::switch::PortLocation;
use harmony_secret::{Secret, SecretManager};
use serde::{Deserialize, Serialize};
#[derive(Secret, Clone, Debug, Serialize, Deserialize)]
struct BrocadeSwitchAuth {
username: String,
password: String,
}
#[tokio::main]
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(192, 168, 55, 101)); // brocade @ sto1
let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
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 config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
.await
.unwrap();
let brocade = brocade::init(
&switch_addresses,
22,
"admin",
"password",
&config.username,
&config.password,
Some(BrocadeOptions {
dry_run: true,
..Default::default()

View File

@@ -1,7 +1,7 @@
use super::BrocadeClient;
use crate::{
BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry,
PortChannelId, parse_brocade_mac_address, shell::BrocadeShell,
PortChannelId, PortOperatingMode, parse_brocade_mac_address, shell::BrocadeShell,
};
use async_trait::async_trait;
@@ -105,11 +105,6 @@ impl BrocadeClient for FastIronClient {
Ok(self.version.clone())
}
async fn setup(&self) -> Result<(), Error> {
// Nothing to do, for now
Ok(())
}
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
info!("[Brocade] Showing MAC address table...");
@@ -142,6 +137,13 @@ impl BrocadeClient for FastIronClient {
todo!()
}
async fn configure_interfaces(
&self,
_interfaces: Vec<(String, PortOperatingMode)>,
) -> Result<(), Error> {
todo!()
}
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
info!("[Brocade] Finding next available channel id...");

View File

@@ -173,18 +173,6 @@ pub trait BrocadeClient {
/// A `BrocadeInfo` structure containing parsed OS type and version string.
async fn version(&self) -> Result<BrocadeInfo, 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)
@@ -215,6 +203,12 @@ pub trait BrocadeClient {
/// A vector of `InterfaceInfo` structures.
async fn get_interfaces(&self) -> Result<Vec<InterfaceInfo>, Error>;
/// Configures a set of interfaces to be operated with a specified mode (access ports, ISL, etc.).
async fn configure_interfaces(
&self,
interfaces: Vec<(String, PortOperatingMode)>,
) -> Result<(), Error>;
/// Scans the existing configuration to find the next available (unused)
/// Port-Channel ID (`lag` or `trunk`) for assignment.
///

View File

@@ -5,9 +5,9 @@ use harmony_types::switch::{PortDeclaration, PortLocation};
use log::debug;
use crate::{
BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceInfo, InterfaceStatus,
InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode, parse_brocade_mac_address,
shell::BrocadeShell,
BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo,
InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
parse_brocade_mac_address, shell::BrocadeShell,
};
pub struct NetworkOperatingSystemClient {
@@ -117,17 +117,10 @@ impl BrocadeClient for NetworkOperatingSystemClient {
Ok(self.version.clone())
}
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<Vec<MacAddressEntry>, Error> {
let output = self
.shell
.run_command("show mac-address-table", crate::ExecutionMode::Regular)
.run_command("show mac-address-table", ExecutionMode::Regular)
.await?;
output
@@ -140,7 +133,7 @@ impl BrocadeClient for NetworkOperatingSystemClient {
async fn get_stack_topology(&self) -> Result<Vec<InterSwitchLink>, Error> {
let output = self
.shell
.run_command("show fabric isl", crate::ExecutionMode::Regular)
.run_command("show fabric isl", ExecutionMode::Regular)
.await?;
output
@@ -155,7 +148,7 @@ impl BrocadeClient for NetworkOperatingSystemClient {
.shell
.run_command(
"show interface status rbridge-id all",
crate::ExecutionMode::Regular,
ExecutionMode::Regular,
)
.await?;
@@ -166,20 +159,64 @@ impl BrocadeClient for NetworkOperatingSystemClient {
.collect()
}
async fn configure_interfaces(
&self,
interfaces: Vec<(String, PortOperatingMode)>,
) -> Result<(), Error> {
let mut commands = vec!["configure terminal".to_string()];
for interface in interfaces {
commands.push(format!("interface {}", interface.0));
match interface.1 {
PortOperatingMode::Fabric => {
commands.push("fabric isl enable".into());
commands.push("fabric trunk enable".into());
}
PortOperatingMode::Trunk => {
commands.push("switchport".into());
commands.push("switchport mode trunk".into());
commands.push("no spanning-tree shutdown".into());
commands.push("no fabric isl enable".into());
commands.push("no fabric trunk enable".into());
}
PortOperatingMode::Access => {
commands.push("switchport".into());
commands.push("switchport mode access".into());
commands.push("switchport access vlan 1".into());
commands.push("no spanning-tree shutdown".into());
commands.push("no fabric isl enable".into());
commands.push("no fabric trunk enable".into());
}
}
commands.push("no shutdown".into());
commands.push("exit".into());
}
commands.push("write memory".into());
self.shell
.run_commands(commands, ExecutionMode::Regular)
.await?;
Ok(())
}
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
todo!()
}
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!()
}
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
async fn clear_port_channel(&self, _channel_name: &str) -> Result<(), Error> {
todo!()
}
}