213 lines
6.0 KiB
Rust
213 lines
6.0 KiB
Rust
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<Result<MacAddressEntry, Error>> {
|
|
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<Result<InterSwitchLink, Error>> {
|
|
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<String> {
|
|
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<BrocadeInfo, Error> {
|
|
Ok(self.version.clone())
|
|
}
|
|
|
|
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, 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<Vec<InterSwitchLink>, 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<Vec<InterfaceInfo>, Error> {
|
|
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...");
|
|
|
|
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<u8> = 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(())
|
|
}
|
|
}
|