WIP: configure-switch #159

Closed
johnride wants to merge 18 commits from configure-switch into master
5 changed files with 127 additions and 24 deletions
Showing only changes of commit 073cccde2f - Show all commits

View File

@ -1,25 +1,33 @@
use std::net::{IpAddr, Ipv4Addr};
use brocade::BrocadeOptions;
use harmony_types::switch::PortLocation;
#[tokio::main]
async fn main() {
env_logger::init();
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 switch_addresses = vec![ip];
let brocade = brocade::init(&switch_addresses, 22, "admin", "password", None)
.await
.expect("Brocade client failed to connect");
let brocade = brocade::init(
&switch_addresses,
22,
"admin",
"password",
Some(BrocadeOptions {
dry_run: true,
..Default::default()
}),
)
.await
.expect("Brocade client failed to connect");
let version = brocade.version().await.unwrap();
println!("Version: {version:?}");
println!("--------------");
println!("Showing MAC Address table...");
let mac_adddresses = brocade.show_mac_address_table().await.unwrap();
println!("VLAN\tMAC\t\t\tPORT");
for mac in mac_adddresses {
@ -28,27 +36,16 @@ async fn main() {
println!("--------------");
let channel_name = "HARMONY_LAG";
println!("Clearing port channel '{channel_name}'...");
brocade.clear_port_channel(channel_name).await.unwrap();
println!("Cleared");
println!("--------------");
println!("Finding next available channel...");
let channel_id = brocade.find_available_channel_id().await.unwrap();
println!("Channel id: {channel_id}");
println!("--------------");
let channel_name = "HARMONY_LAG";
let ports = [PortLocation(1, 1, 3), PortLocation(1, 1, 4)];
println!("Creating port channel '{channel_name}' with ports {ports:?}'...");
brocade
.create_port_channel(channel_id, channel_name, &ports)
.await
.unwrap();
println!("Created");
}

View File

@ -145,7 +145,7 @@ impl BrocadeClient for FastIronClient {
}
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
debug!("[Brocade] Clearing port-channel: {channel_name}");
info!("[Brocade] Clearing port-channel: {channel_name}");
let commands = vec![
"configure terminal".to_string(),
@ -156,6 +156,7 @@ impl BrocadeClient for FastIronClient {
.run_commands(commands, ExecutionMode::Privileged)
.await?;
info!("[Brocade] Port-channel '{channel_name}' cleared.");
Ok(())
}
}

View File

@ -4,6 +4,7 @@ use std::{
time::Duration,
};
use crate::network_operating_system::NetworkOperatingSystemClient;
use crate::{
fast_iron::FastIronClient,
shell::{BrocadeSession, BrocadeShell},
@ -15,6 +16,7 @@ use harmony_types::switch::{PortDeclaration, PortLocation};
use regex::Regex;
mod fast_iron;
mod network_operating_system;
mod shell;
mod ssh;
@ -36,7 +38,7 @@ pub struct TimeoutConfig {
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
shell_ready: Duration::from_secs(3),
shell_ready: Duration::from_secs(10),
command_execution: Duration::from_secs(60), // Commands like `deploy` (for a LAG) can take a while
cleanup: Duration::from_secs(10),
message_wait: Duration::from_millis(500),
@ -91,7 +93,10 @@ pub async fn init(
shell,
version: version_info,
}),
BrocadeOs::NetworkOperatingSystem => todo!(),
BrocadeOs::NetworkOperatingSystem => Box::new(NetworkOperatingSystemClient {
shell,
version: version_info,
}),
BrocadeOs::Unknown => todo!(),
})
}

View File

@ -0,0 +1,87 @@
use std::str::FromStr;
use async_trait::async_trait;
use harmony_types::switch::{PortDeclaration, PortLocation};
use log::debug;
use crate::{
BrocadeClient, BrocadeInfo, Error, MacAddressEntry, PortChannelId, parse_brocade_mac_address,
shell::BrocadeShell,
};
pub struct NetworkOperatingSystemClient {
pub shell: BrocadeShell,
pub version: BrocadeInfo,
}
impl NetworkOperatingSystemClient {
pub 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)),
}
}
}
#[async_trait]
impl BrocadeClient for NetworkOperatingSystemClient {
async fn version(&self) -> Result<BrocadeInfo, Error> {
Ok(self.version.clone())
}
async fn show_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
let output = self
.shell
.run_command("show mac-address-table", crate::ExecutionMode::Regular)
.await?;
output
.lines()
.skip(1)
.filter_map(|line| self.parse_mac_entry(line))
.collect()
}
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],
) -> Result<(), Error> {
todo!()
}
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
todo!()
}
}

View File

@ -148,6 +148,10 @@ impl BrocadeSession {
}
pub async fn run_command(&mut self, command: &str) -> Result<String, Error> {
if self.should_skip_command(command) {
return Ok(String::new());
}
debug!("[Brocade] Running command: '{command}'...");
self.channel
@ -170,7 +174,15 @@ impl BrocadeSession {
Ok(())
}
pub async fn collect_command_output(&mut self) -> Result<Vec<u8>, Error> {
fn should_skip_command(&self, command: &str) -> bool {
if (command.starts_with("write") || command.starts_with("deploy")) && self.options.dry_run {
info!("[Brocade] Dry-run mode enabled, skipping command: {command}");
return true;
}
false
}
async fn collect_command_output(&mut self) -> Result<Vec<u8>, Error> {
let mut output = Vec::new();
let start = Instant::now();
let read_timeout = Duration::from_millis(500);
@ -222,7 +234,7 @@ impl BrocadeSession {
Ok(output)
}
pub fn check_for_command_errors(&self, output: &str, command: &str) -> Result<(), Error> {
fn check_for_command_errors(&self, output: &str, command: &str) -> Result<(), Error> {
const ERROR_PATTERNS: &[&str] = &[
"invalid input",
"syntax error",
@ -254,7 +266,7 @@ impl BrocadeSession {
}
}
pub async fn wait_for_shell_ready(
async fn wait_for_shell_ready(
channel: &mut russh::Channel<russh::client::Msg>,
timeouts: &TimeoutConfig,
) -> Result<(), Error> {
@ -266,6 +278,7 @@ pub async fn wait_for_shell_ready(
Ok(Some(ChannelMsg::Data { data })) => {
buffer.extend_from_slice(&data);
let output = String::from_utf8_lossy(&buffer);
let output = output.trim();
if output.ends_with('>') || output.ends_with('#') {
debug!("[Brocade] Shell ready");
return Ok(());
@ -279,7 +292,7 @@ pub async fn wait_for_shell_ready(
Ok(())
}
pub async fn try_elevate_session(
async fn try_elevate_session(
channel: &mut russh::Channel<russh::client::Msg>,
username: &str,
password: &str,