WIP: configure-switch #159
@ -1,16 +1,26 @@
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
use brocade::BrocadeOptions;
|
||||||
use harmony_types::switch::PortLocation;
|
use harmony_types::switch::PortLocation;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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(10, 0, 0, 250)); // old brocade @ ianlet
|
||||||
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 55, 101)); // brocade @ sto1
|
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 55, 101)); // brocade @ sto1
|
||||||
let switch_addresses = vec![ip];
|
let switch_addresses = vec![ip];
|
||||||
|
|
||||||
let brocade = brocade::init(&switch_addresses, 22, "admin", "password", None)
|
let brocade = brocade::init(
|
||||||
|
&switch_addresses,
|
||||||
|
22,
|
||||||
|
"admin",
|
||||||
|
"password",
|
||||||
|
Some(BrocadeOptions {
|
||||||
|
dry_run: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Brocade client failed to connect");
|
.expect("Brocade client failed to connect");
|
||||||
|
|
||||||
@ -18,8 +28,6 @@ async fn main() {
|
|||||||
println!("Version: {version:?}");
|
println!("Version: {version:?}");
|
||||||
|
|
||||||
println!("--------------");
|
println!("--------------");
|
||||||
println!("Showing MAC Address table...");
|
|
||||||
|
|
||||||
let mac_adddresses = brocade.show_mac_address_table().await.unwrap();
|
let mac_adddresses = brocade.show_mac_address_table().await.unwrap();
|
||||||
println!("VLAN\tMAC\t\t\tPORT");
|
println!("VLAN\tMAC\t\t\tPORT");
|
||||||
for mac in mac_adddresses {
|
for mac in mac_adddresses {
|
||||||
@ -28,27 +36,16 @@ async fn main() {
|
|||||||
|
|
||||||
println!("--------------");
|
println!("--------------");
|
||||||
let channel_name = "HARMONY_LAG";
|
let channel_name = "HARMONY_LAG";
|
||||||
println!("Clearing port channel '{channel_name}'...");
|
|
||||||
|
|
||||||
brocade.clear_port_channel(channel_name).await.unwrap();
|
brocade.clear_port_channel(channel_name).await.unwrap();
|
||||||
|
|
||||||
println!("Cleared");
|
|
||||||
|
|
||||||
println!("--------------");
|
println!("--------------");
|
||||||
println!("Finding next available channel...");
|
|
||||||
|
|
||||||
let channel_id = brocade.find_available_channel_id().await.unwrap();
|
let channel_id = brocade.find_available_channel_id().await.unwrap();
|
||||||
println!("Channel id: {channel_id}");
|
|
||||||
|
|
||||||
println!("--------------");
|
println!("--------------");
|
||||||
let channel_name = "HARMONY_LAG";
|
let channel_name = "HARMONY_LAG";
|
||||||
let ports = [PortLocation(1, 1, 3), PortLocation(1, 1, 4)];
|
let ports = [PortLocation(1, 1, 3), PortLocation(1, 1, 4)];
|
||||||
println!("Creating port channel '{channel_name}' with ports {ports:?}'...");
|
|
||||||
|
|
||||||
brocade
|
brocade
|
||||||
.create_port_channel(channel_id, channel_name, &ports)
|
.create_port_channel(channel_id, channel_name, &ports)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("Created");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,7 +145,7 @@ impl BrocadeClient for FastIronClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
|
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![
|
let commands = vec![
|
||||||
"configure terminal".to_string(),
|
"configure terminal".to_string(),
|
||||||
@ -156,6 +156,7 @@ impl BrocadeClient for FastIronClient {
|
|||||||
.run_commands(commands, ExecutionMode::Privileged)
|
.run_commands(commands, ExecutionMode::Privileged)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
info!("[Brocade] Port-channel '{channel_name}' cleared.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::network_operating_system::NetworkOperatingSystemClient;
|
||||||
use crate::{
|
use crate::{
|
||||||
fast_iron::FastIronClient,
|
fast_iron::FastIronClient,
|
||||||
shell::{BrocadeSession, BrocadeShell},
|
shell::{BrocadeSession, BrocadeShell},
|
||||||
@ -15,6 +16,7 @@ use harmony_types::switch::{PortDeclaration, PortLocation};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
mod fast_iron;
|
mod fast_iron;
|
||||||
|
mod network_operating_system;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod ssh;
|
mod ssh;
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ pub struct TimeoutConfig {
|
|||||||
impl Default for TimeoutConfig {
|
impl Default for TimeoutConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
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
|
command_execution: Duration::from_secs(60), // Commands like `deploy` (for a LAG) can take a while
|
||||||
cleanup: Duration::from_secs(10),
|
cleanup: Duration::from_secs(10),
|
||||||
message_wait: Duration::from_millis(500),
|
message_wait: Duration::from_millis(500),
|
||||||
@ -91,7 +93,10 @@ pub async fn init(
|
|||||||
shell,
|
shell,
|
||||||
version: version_info,
|
version: version_info,
|
||||||
}),
|
}),
|
||||||
BrocadeOs::NetworkOperatingSystem => todo!(),
|
BrocadeOs::NetworkOperatingSystem => Box::new(NetworkOperatingSystemClient {
|
||||||
|
shell,
|
||||||
|
version: version_info,
|
||||||
|
}),
|
||||||
BrocadeOs::Unknown => todo!(),
|
BrocadeOs::Unknown => todo!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
87
brocade/src/network_operating_system.rs
Normal file
87
brocade/src/network_operating_system.rs
Normal 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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -148,6 +148,10 @@ impl BrocadeSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_command(&mut self, command: &str) -> Result<String, Error> {
|
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}'...");
|
debug!("[Brocade] Running command: '{command}'...");
|
||||||
|
|
||||||
self.channel
|
self.channel
|
||||||
@ -170,7 +174,15 @@ impl BrocadeSession {
|
|||||||
Ok(())
|
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 mut output = Vec::new();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let read_timeout = Duration::from_millis(500);
|
let read_timeout = Duration::from_millis(500);
|
||||||
@ -222,7 +234,7 @@ impl BrocadeSession {
|
|||||||
Ok(output)
|
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] = &[
|
const ERROR_PATTERNS: &[&str] = &[
|
||||||
"invalid input",
|
"invalid input",
|
||||||
"syntax error",
|
"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>,
|
channel: &mut russh::Channel<russh::client::Msg>,
|
||||||
timeouts: &TimeoutConfig,
|
timeouts: &TimeoutConfig,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -266,6 +278,7 @@ pub async fn wait_for_shell_ready(
|
|||||||
Ok(Some(ChannelMsg::Data { data })) => {
|
Ok(Some(ChannelMsg::Data { data })) => {
|
||||||
buffer.extend_from_slice(&data);
|
buffer.extend_from_slice(&data);
|
||||||
let output = String::from_utf8_lossy(&buffer);
|
let output = String::from_utf8_lossy(&buffer);
|
||||||
|
let output = output.trim();
|
||||||
if output.ends_with('>') || output.ends_with('#') {
|
if output.ends_with('>') || output.ends_with('#') {
|
||||||
debug!("[Brocade] Shell ready");
|
debug!("[Brocade] Shell ready");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -279,7 +292,7 @@ pub async fn wait_for_shell_ready(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_elevate_session(
|
async fn try_elevate_session(
|
||||||
channel: &mut russh::Channel<russh::client::Msg>,
|
channel: &mut russh::Channel<russh::client::Msg>,
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user