Some checks failed
Run Check Script / check (pull_request) Has been cancelled
307 lines
9.5 KiB
Rust
307 lines
9.5 KiB
Rust
use std::str::FromStr;
|
|
|
|
use async_trait::async_trait;
|
|
use harmony_types::switch::{PortDeclaration, PortLocation};
|
|
use log::{debug, info};
|
|
|
|
use crate::{
|
|
BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo,
|
|
InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
|
|
parse_brocade_mac_address, shell::BrocadeShell,
|
|
};
|
|
|
|
pub struct NetworkOperatingSystemClient {
|
|
shell: BrocadeShell,
|
|
version: BrocadeInfo,
|
|
}
|
|
|
|
impl NetworkOperatingSystemClient {
|
|
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<Result<MacAddressEntry, Error>> {
|
|
debug!("[Brocade] Parsing mac address entry: {line}");
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() < 5 {
|
|
return None;
|
|
}
|
|
|
|
let (vlan, mac_address, port) = match parts.len() {
|
|
5 => (
|
|
u16::from_str(parts[0]).ok()?,
|
|
parse_brocade_mac_address(parts[1]).ok()?,
|
|
parts[4].to_string(),
|
|
),
|
|
_ => (
|
|
u16::from_str(parts[0]).ok()?,
|
|
parse_brocade_mac_address(parts[1]).ok()?,
|
|
parts[5].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_inter_switch_link_entry(&self, line: &str) -> Option<Result<InterSwitchLink, Error>> {
|
|
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<Result<InterfaceInfo, Error>> {
|
|
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]
|
|
impl BrocadeClient for NetworkOperatingSystemClient {
|
|
async fn version(&self) -> Result<BrocadeInfo, Error> {
|
|
Ok(self.version.clone())
|
|
}
|
|
|
|
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
|
let output = self
|
|
.shell
|
|
.run_command("show mac-address-table", ExecutionMode::Regular)
|
|
.await?;
|
|
|
|
output
|
|
.lines()
|
|
.skip(1)
|
|
.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 fabric isl", ExecutionMode::Regular)
|
|
.await?;
|
|
|
|
output
|
|
.lines()
|
|
.skip(6)
|
|
.filter_map(|line| self.parse_inter_switch_link_entry(line))
|
|
.collect()
|
|
}
|
|
|
|
async fn get_interfaces(&self) -> Result<Vec<InterfaceInfo>, Error> {
|
|
let output = self
|
|
.shell
|
|
.run_command(
|
|
"show interface status rbridge-id all",
|
|
ExecutionMode::Regular,
|
|
)
|
|
.await?;
|
|
|
|
output
|
|
.lines()
|
|
.skip(2)
|
|
.filter_map(|line| self.parse_interface_status_entry(line))
|
|
.collect()
|
|
}
|
|
|
|
async fn configure_interfaces(
|
|
&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 {
|
|
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?;
|
|
|
|
info!("[Brocade] Interfaces configured.");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
|
info!("[Brocade] Finding next available channel id...");
|
|
|
|
let output = self
|
|
.shell
|
|
.run_command("show port-channel", ExecutionMode::Regular)
|
|
.await?;
|
|
|
|
let used_ids: Vec<u8> = 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],
|
|
) -> Result<(), Error> {
|
|
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 {channel_id} mode active"));
|
|
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> {
|
|
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(())
|
|
}
|
|
}
|