add option to run brocade commands in dry-run
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -6,8 +7,11 @@ use std::{
|
||||
use async_trait::async_trait;
|
||||
use harmony_types::net::{IpAddress, MacAddress};
|
||||
use log::{debug, info};
|
||||
use russh::client::{Handle, Handler};
|
||||
use russh_keys::key;
|
||||
use russh::{
|
||||
client::{Handle, Handler},
|
||||
kex::DH_G1_SHA1,
|
||||
};
|
||||
use russh_keys::key::{self, SSH_RSA};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
@@ -19,6 +23,30 @@ pub struct MacAddressEntry {
|
||||
|
||||
pub struct BrocadeClient {
|
||||
client: Handle<Client>,
|
||||
options: BrocadeOptions,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct BrocadeOptions {
|
||||
pub dry_run: bool,
|
||||
pub ssh: SshOptions,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SshOptions {
|
||||
pub preferred_algorithms: russh::Preferred,
|
||||
}
|
||||
|
||||
impl Default for SshOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
preferred_algorithms: russh::Preferred {
|
||||
kex: Cow::Borrowed(&[DH_G1_SHA1]),
|
||||
key: Cow::Borrowed(&[SSH_RSA]),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BrocadeClient {
|
||||
@@ -26,14 +54,25 @@ impl BrocadeClient {
|
||||
ip_addresses: &[IpAddress],
|
||||
username: &str,
|
||||
password: &str,
|
||||
options: Option<BrocadeOptions>,
|
||||
) -> Result<Self, Error> {
|
||||
let ip = ip_addresses[0]; // FIXME: Find a better way to get master switch IP address
|
||||
if ip_addresses.is_empty() {
|
||||
return Err(Error::ConfigurationError(
|
||||
"No IP addresses provided".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let config = russh::client::Config::default();
|
||||
let ip = ip_addresses[0]; // FIXME: Find a better way to get master switch IP address
|
||||
let options = options.unwrap_or_default();
|
||||
|
||||
let config = russh::client::Config {
|
||||
preferred: options.ssh.preferred_algorithms.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let mut client = russh::client::connect(Arc::new(config), (ip, 22), Client {}).await?;
|
||||
|
||||
match client.authenticate_password(username, password).await? {
|
||||
true => Ok(Self { client }),
|
||||
true => Ok(Self { client, options }),
|
||||
false => Err(Error::AuthenticationError(
|
||||
"ssh authentication failed".to_string(),
|
||||
)),
|
||||
@@ -41,7 +80,7 @@ impl BrocadeClient {
|
||||
}
|
||||
|
||||
pub async fn show_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
||||
let output = self.run_command("show mac-address-table").await?;
|
||||
let output = self.run_command("show mac-address").await?;
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// The Brocade output usually has a header and then one entry per line.
|
||||
@@ -104,7 +143,7 @@ impl BrocadeClient {
|
||||
pub async fn find_available_channel_id(&self) -> Result<u8, Error> {
|
||||
debug!("[Brocade] Finding next available channel id...");
|
||||
|
||||
let output = self.run_command("show port-channel summary").await?;
|
||||
let output = self.run_command("show lag").await?;
|
||||
let mut used_ids = Vec::new();
|
||||
|
||||
// Sample output line: "3 Po3(SU) LACP Eth Yes 128/128 active "
|
||||
@@ -121,7 +160,7 @@ impl BrocadeClient {
|
||||
// Sort the used IDs to find the next available number.
|
||||
used_ids.sort();
|
||||
|
||||
let mut next_id = 1;
|
||||
let mut next_id = 0;
|
||||
for &id in &used_ids {
|
||||
if id == next_id {
|
||||
next_id += 1;
|
||||
@@ -136,6 +175,11 @@ impl BrocadeClient {
|
||||
}
|
||||
|
||||
async fn run_command(&self, command: &str) -> Result<String, Error> {
|
||||
if !command.starts_with("show") && self.options.dry_run {
|
||||
info!("[Brocade] Dry-run mode enabled, skipping command: {command}");
|
||||
return Ok("".into());
|
||||
}
|
||||
|
||||
debug!("[Brocade] Running command: '{command}'...");
|
||||
|
||||
let mut channel = self.client.channel_open_session().await?;
|
||||
@@ -161,9 +205,13 @@ impl BrocadeClient {
|
||||
)));
|
||||
}
|
||||
}
|
||||
russh::ChannelMsg::Success
|
||||
| russh::ChannelMsg::WindowAdjusted { .. }
|
||||
| russh::ChannelMsg::Eof => {}
|
||||
russh::ChannelMsg::Eof => {
|
||||
channel.close().await?;
|
||||
}
|
||||
russh::ChannelMsg::Close => {
|
||||
break;
|
||||
}
|
||||
russh::ChannelMsg::Success | russh::ChannelMsg::WindowAdjusted { .. } => {}
|
||||
_ => {
|
||||
return Err(Error::UnexpectedError(format!(
|
||||
"Russh got unexpected msg {msg:?}"
|
||||
@@ -172,20 +220,25 @@ impl BrocadeClient {
|
||||
}
|
||||
}
|
||||
|
||||
channel.close().await?;
|
||||
|
||||
let output = String::from_utf8(output).expect("Output should be UTF-8 compatible");
|
||||
debug!("[Brocade] Command output:\n{output}");
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
async fn run_commands(&self, commands: Vec<String>) -> Result<(), Error> {
|
||||
let mut channel = self.client.channel_open_session().await?;
|
||||
|
||||
// Execute commands sequentially and check for errors immediately.
|
||||
for command in commands {
|
||||
if !command.starts_with("show") && self.options.dry_run {
|
||||
info!("[Brocade] Dry-run mode enabled, skipping command: {command}");
|
||||
continue;
|
||||
}
|
||||
|
||||
debug!("[Brocade] Running command: '{command}'...");
|
||||
|
||||
let mut channel = self.client.channel_open_session().await?;
|
||||
let mut output = Vec::new();
|
||||
let mut close_received = false;
|
||||
|
||||
channel.exec(true, command.as_str()).await?;
|
||||
|
||||
loop {
|
||||
@@ -206,12 +259,30 @@ impl BrocadeClient {
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {} // Ignore other messages like success or EOF for now.
|
||||
russh::ChannelMsg::Eof => {
|
||||
channel.close().await?;
|
||||
}
|
||||
russh::ChannelMsg::Close => {
|
||||
close_received = true;
|
||||
break;
|
||||
}
|
||||
russh::ChannelMsg::Success | russh::ChannelMsg::WindowAdjusted { .. } => {}
|
||||
_ => {
|
||||
return Err(Error::UnexpectedError(format!(
|
||||
"Russh got unexpected msg {msg:?}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !close_received {
|
||||
return Err(Error::UnexpectedError(format!(
|
||||
"Channel closed without receiving a final CLOSE message for command: {}",
|
||||
command
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
channel.close().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user