improve wait for shell ready, restore dry run mode
All checks were successful
Run Check Script / check (pull_request) Successful in 1m12s

This commit is contained in:
Ian Letourneau
2025-10-08 10:36:02 -04:00
parent 77e09436a9
commit 073cccde2f
5 changed files with 127 additions and 24 deletions

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,