implement switch brocade setup by configuring interfaces with operating mode access
All checks were successful
Run Check Script / check (pull_request) Successful in 1m6s
All checks were successful
Run Check Script / check (pull_request) Successful in 1m6s
This commit is contained in:
parent
1265cebfa7
commit
da5be17cb6
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -680,11 +680,13 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"harmony_secret",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"russh",
|
"russh",
|
||||||
"russh-keys",
|
"russh-keys",
|
||||||
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2363,6 +2365,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"opnsense-config",
|
"opnsense-config",
|
||||||
"opnsense-config-xml",
|
"opnsense-config-xml",
|
||||||
|
"option-ext",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"russh",
|
"russh",
|
||||||
|
@ -14,3 +14,5 @@ tokio.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
regex = "1.11.3"
|
regex = "1.11.3"
|
||||||
|
harmony_secret = { path = "../harmony_secret" }
|
||||||
|
serde.workspace = true
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
use brocade::BrocadeOptions;
|
use brocade::BrocadeOptions;
|
||||||
use harmony_types::switch::PortLocation;
|
use harmony_secret::{Secret, SecretManager};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Secret, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct BrocadeSwitchAuth {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).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 ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
|
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
|
||||||
let switch_addresses = vec![ip];
|
let switch_addresses = vec![ip];
|
||||||
|
|
||||||
|
let config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let brocade = brocade::init(
|
let brocade = brocade::init(
|
||||||
&switch_addresses,
|
&switch_addresses,
|
||||||
22,
|
22,
|
||||||
"admin",
|
&config.username,
|
||||||
"password",
|
&config.password,
|
||||||
Some(BrocadeOptions {
|
Some(BrocadeOptions {
|
||||||
dry_run: true,
|
dry_run: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::BrocadeClient;
|
use super::BrocadeClient;
|
||||||
use crate::{
|
use crate::{
|
||||||
BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry,
|
BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry,
|
||||||
PortChannelId, parse_brocade_mac_address, shell::BrocadeShell,
|
PortChannelId, PortOperatingMode, parse_brocade_mac_address, shell::BrocadeShell,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -105,11 +105,6 @@ impl BrocadeClient for FastIronClient {
|
|||||||
Ok(self.version.clone())
|
Ok(self.version.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), Error> {
|
|
||||||
// Nothing to do, for now
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
||||||
info!("[Brocade] Showing MAC address table...");
|
info!("[Brocade] Showing MAC address table...");
|
||||||
|
|
||||||
@ -142,6 +137,13 @@ impl BrocadeClient for FastIronClient {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn configure_interfaces(
|
||||||
|
&self,
|
||||||
|
_interfaces: Vec<(String, PortOperatingMode)>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
||||||
info!("[Brocade] Finding next available channel id...");
|
info!("[Brocade] Finding next available channel id...");
|
||||||
|
|
||||||
|
@ -173,18 +173,6 @@ pub trait BrocadeClient {
|
|||||||
/// A `BrocadeInfo` structure containing parsed OS type and version string.
|
/// A `BrocadeInfo` structure containing parsed OS type and version string.
|
||||||
async fn version(&self) -> Result<BrocadeInfo, Error>;
|
async fn version(&self) -> Result<BrocadeInfo, Error>;
|
||||||
|
|
||||||
/// Executes essential, idempotent, one-time initial configuration steps.
|
|
||||||
///
|
|
||||||
/// This is an opiniated procedure that setups a switch to provide high availability
|
|
||||||
/// capabilities as decided by the NationTech team.
|
|
||||||
///
|
|
||||||
/// This includes tasks like ensuring necessary feature licenses are active,
|
|
||||||
/// enabling switchport for all interfaces except the ones intended for Fabric Networking, etc.
|
|
||||||
///
|
|
||||||
/// The implementation must ensure the operation is **idempotent** (safe to run multiple times)
|
|
||||||
/// and that it doesn't break existing configurations.
|
|
||||||
async fn setup(&self) -> Result<(), Error>;
|
|
||||||
|
|
||||||
/// Retrieves the dynamically learned MAC address table from the switch.
|
/// Retrieves the dynamically learned MAC address table from the switch.
|
||||||
///
|
///
|
||||||
/// This is crucial for discovering where specific network endpoints (MAC addresses)
|
/// This is crucial for discovering where specific network endpoints (MAC addresses)
|
||||||
@ -215,6 +203,12 @@ pub trait BrocadeClient {
|
|||||||
/// A vector of `InterfaceInfo` structures.
|
/// A vector of `InterfaceInfo` structures.
|
||||||
async fn get_interfaces(&self) -> Result<Vec<InterfaceInfo>, Error>;
|
async fn get_interfaces(&self) -> Result<Vec<InterfaceInfo>, Error>;
|
||||||
|
|
||||||
|
/// Configures a set of interfaces to be operated with a specified mode (access ports, ISL, etc.).
|
||||||
|
async fn configure_interfaces(
|
||||||
|
&self,
|
||||||
|
interfaces: Vec<(String, PortOperatingMode)>,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Scans the existing configuration to find the next available (unused)
|
/// Scans the existing configuration to find the next available (unused)
|
||||||
/// Port-Channel ID (`lag` or `trunk`) for assignment.
|
/// Port-Channel ID (`lag` or `trunk`) for assignment.
|
||||||
///
|
///
|
||||||
|
@ -5,9 +5,9 @@ use harmony_types::switch::{PortDeclaration, PortLocation};
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceInfo, InterfaceStatus,
|
BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo,
|
||||||
InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode, parse_brocade_mac_address,
|
InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
|
||||||
shell::BrocadeShell,
|
parse_brocade_mac_address, shell::BrocadeShell,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct NetworkOperatingSystemClient {
|
pub struct NetworkOperatingSystemClient {
|
||||||
@ -117,17 +117,10 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
|||||||
Ok(self.version.clone())
|
Ok(self.version.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), Error> {
|
|
||||||
let links = self.get_stack_topology().await?;
|
|
||||||
let interfaces = self.get_interfaces().await?;
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
||||||
let output = self
|
let output = self
|
||||||
.shell
|
.shell
|
||||||
.run_command("show mac-address-table", crate::ExecutionMode::Regular)
|
.run_command("show mac-address-table", ExecutionMode::Regular)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
output
|
output
|
||||||
@ -140,7 +133,7 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
|||||||
async fn get_stack_topology(&self) -> Result<Vec<InterSwitchLink>, Error> {
|
async fn get_stack_topology(&self) -> Result<Vec<InterSwitchLink>, Error> {
|
||||||
let output = self
|
let output = self
|
||||||
.shell
|
.shell
|
||||||
.run_command("show fabric isl", crate::ExecutionMode::Regular)
|
.run_command("show fabric isl", ExecutionMode::Regular)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
output
|
output
|
||||||
@ -155,7 +148,7 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
|||||||
.shell
|
.shell
|
||||||
.run_command(
|
.run_command(
|
||||||
"show interface status rbridge-id all",
|
"show interface status rbridge-id all",
|
||||||
crate::ExecutionMode::Regular,
|
ExecutionMode::Regular,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -166,20 +159,64 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn configure_interfaces(
|
||||||
|
&self,
|
||||||
|
interfaces: Vec<(String, PortOperatingMode)>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
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?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_port_channel(
|
async fn create_port_channel(
|
||||||
&self,
|
&self,
|
||||||
channel_id: PortChannelId,
|
_channel_id: PortChannelId,
|
||||||
channel_name: &str,
|
_channel_name: &str,
|
||||||
ports: &[PortLocation],
|
_ports: &[PortLocation],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
|
async fn clear_port_channel(&self, _channel_name: &str) -> Result<(), Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ askama.workspace = true
|
|||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
inquire.workspace = true
|
inquire.workspace = true
|
||||||
brocade = { path = "../brocade" }
|
brocade = { path = "../brocade" }
|
||||||
|
option-ext = "0.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
@ -226,6 +226,16 @@ impl Error for SwitchError {}
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SwitchClient: Send + Sync {
|
pub trait SwitchClient: Send + Sync {
|
||||||
|
/// Executes essential, idempotent, one-time initial configuration steps.
|
||||||
|
///
|
||||||
|
/// This is an opiniated procedure that setups a switch to provide high availability
|
||||||
|
/// capabilities as decided by the NationTech team.
|
||||||
|
///
|
||||||
|
/// This includes tasks like enabling switchport for all interfaces
|
||||||
|
/// except the ones intended for Fabric Networking, etc.
|
||||||
|
///
|
||||||
|
/// The implementation must ensure the operation is **idempotent** (safe to run multiple times)
|
||||||
|
/// and that it doesn't break existing configurations.
|
||||||
async fn setup(&self) -> Result<(), SwitchError>;
|
async fn setup(&self) -> Result<(), SwitchError>;
|
||||||
|
|
||||||
async fn find_port(
|
async fn find_port(
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use brocade::{BrocadeClient, BrocadeOptions};
|
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
||||||
use harmony_secret::Secret;
|
use harmony_secret::Secret;
|
||||||
use harmony_types::{
|
use harmony_types::{
|
||||||
net::{IpAddress, MacAddress},
|
net::{IpAddress, MacAddress},
|
||||||
switch::{PortDeclaration, PortLocation},
|
switch::{PortDeclaration, PortLocation},
|
||||||
};
|
};
|
||||||
|
use option_ext::OptionExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::topology::{SwitchClient, SwitchError};
|
use crate::topology::{SwitchClient, SwitchError};
|
||||||
@ -28,10 +29,42 @@ impl BrocadeSwitchClient {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SwitchClient for BrocadeSwitchClient {
|
impl SwitchClient for BrocadeSwitchClient {
|
||||||
async fn setup(&self) -> Result<(), SwitchError> {
|
async fn setup(&self) -> Result<(), SwitchError> {
|
||||||
self.brocade
|
let stack_topology = self
|
||||||
.setup()
|
.brocade
|
||||||
|
.get_stack_topology()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SwitchError::new(e.to_string()))
|
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||||
|
|
||||||
|
let interfaces = self
|
||||||
|
.brocade
|
||||||
|
.get_interfaces()
|
||||||
|
.await
|
||||||
|
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||||
|
|
||||||
|
let interfaces: Vec<(String, PortOperatingMode)> = interfaces
|
||||||
|
.into_iter()
|
||||||
|
.filter(|interface| {
|
||||||
|
interface.operating_mode.is_none() && interface.status == InterfaceStatus::Connected
|
||||||
|
})
|
||||||
|
.filter(|interface| {
|
||||||
|
!stack_topology.iter().any(|link: &InterSwitchLink| {
|
||||||
|
link.local_port == interface.port_location
|
||||||
|
|| link.remote_port.contains(&interface.port_location)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|interface| (interface.name.clone(), PortOperatingMode::Access))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if interfaces.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.brocade
|
||||||
|
.configure_interfaces(interfaces)
|
||||||
|
.await
|
||||||
|
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_port(
|
async fn find_port(
|
||||||
@ -86,3 +119,267 @@ pub struct BrocadeSwitchAuth {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use assertor::*;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use brocade::{
|
||||||
|
BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceInfo, InterfaceStatus,
|
||||||
|
InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
|
||||||
|
};
|
||||||
|
use harmony_types::switch::PortLocation;
|
||||||
|
|
||||||
|
use crate::{infra::brocade::BrocadeSwitchClient, topology::SwitchClient};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn setup_should_configure_ethernet_interfaces_as_access_ports() {
|
||||||
|
let first_interface = given_interface()
|
||||||
|
.with_port_location(PortLocation(1, 0, 1))
|
||||||
|
.build();
|
||||||
|
let second_interface = given_interface()
|
||||||
|
.with_port_location(PortLocation(1, 0, 4))
|
||||||
|
.build();
|
||||||
|
let brocade = Box::new(FakeBrocadeClient::new(
|
||||||
|
vec![],
|
||||||
|
vec![first_interface.clone(), second_interface.clone()],
|
||||||
|
));
|
||||||
|
let client = BrocadeSwitchClient {
|
||||||
|
brocade: brocade.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.setup().await.unwrap();
|
||||||
|
|
||||||
|
let configured_interfaces = brocade.configured_interfaces.lock().unwrap();
|
||||||
|
assert_that!(*configured_interfaces).contains_exactly(vec![
|
||||||
|
(first_interface.name.clone(), PortOperatingMode::Access),
|
||||||
|
(second_interface.name.clone(), PortOperatingMode::Access),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn setup_with_an_already_configured_interface_should_skip_configuration() {
|
||||||
|
let brocade = Box::new(FakeBrocadeClient::new(
|
||||||
|
vec![],
|
||||||
|
vec![
|
||||||
|
given_interface()
|
||||||
|
.with_operating_mode(Some(PortOperatingMode::Access))
|
||||||
|
.build(),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
let client = BrocadeSwitchClient {
|
||||||
|
brocade: brocade.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.setup().await.unwrap();
|
||||||
|
|
||||||
|
let configured_interfaces = brocade.configured_interfaces.lock().unwrap();
|
||||||
|
assert_that!(*configured_interfaces).is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn setup_with_a_disconnected_interface_should_skip_configuration() {
|
||||||
|
let brocade = Box::new(FakeBrocadeClient::new(
|
||||||
|
vec![],
|
||||||
|
vec![
|
||||||
|
given_interface()
|
||||||
|
.with_status(InterfaceStatus::SfpAbsent)
|
||||||
|
.build(),
|
||||||
|
given_interface()
|
||||||
|
.with_status(InterfaceStatus::NotConnected)
|
||||||
|
.build(),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
let client = BrocadeSwitchClient {
|
||||||
|
brocade: brocade.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.setup().await.unwrap();
|
||||||
|
|
||||||
|
let configured_interfaces = brocade.configured_interfaces.lock().unwrap();
|
||||||
|
assert_that!(*configured_interfaces).is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn setup_with_inter_switch_links_should_not_configure_interfaces_used_to_form_stack() {
|
||||||
|
let brocade = Box::new(FakeBrocadeClient::new(
|
||||||
|
vec![
|
||||||
|
given_inter_switch_link()
|
||||||
|
.between(PortLocation(1, 0, 1), PortLocation(2, 0, 1))
|
||||||
|
.build(),
|
||||||
|
given_inter_switch_link()
|
||||||
|
.between(PortLocation(2, 0, 2), PortLocation(3, 0, 1))
|
||||||
|
.build(),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
given_interface()
|
||||||
|
.with_port_location(PortLocation(1, 0, 1))
|
||||||
|
.build(),
|
||||||
|
given_interface()
|
||||||
|
.with_port_location(PortLocation(2, 0, 1))
|
||||||
|
.build(),
|
||||||
|
given_interface()
|
||||||
|
.with_port_location(PortLocation(3, 0, 1))
|
||||||
|
.build(),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
let client = BrocadeSwitchClient {
|
||||||
|
brocade: brocade.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.setup().await.unwrap();
|
||||||
|
|
||||||
|
let configured_interfaces = brocade.configured_interfaces.lock().unwrap();
|
||||||
|
assert_that!(*configured_interfaces).is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct FakeBrocadeClient {
|
||||||
|
stack_topology: Vec<InterSwitchLink>,
|
||||||
|
interfaces: Vec<InterfaceInfo>,
|
||||||
|
configured_interfaces: Arc<Mutex<Vec<(String, PortOperatingMode)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl BrocadeClient for FakeBrocadeClient {
|
||||||
|
async fn version(&self) -> Result<BrocadeInfo, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_stack_topology(&self) -> Result<Vec<InterSwitchLink>, Error> {
|
||||||
|
Ok(self.stack_topology.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_interfaces(&self) -> Result<Vec<InterfaceInfo>, Error> {
|
||||||
|
Ok(self.interfaces.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_interfaces(
|
||||||
|
&self,
|
||||||
|
interfaces: Vec<(String, PortOperatingMode)>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut configured_interfaces = self.configured_interfaces.lock().unwrap();
|
||||||
|
*configured_interfaces = interfaces;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeBrocadeClient {
|
||||||
|
fn new(stack_topology: Vec<InterSwitchLink>, interfaces: Vec<InterfaceInfo>) -> Self {
|
||||||
|
Self {
|
||||||
|
stack_topology,
|
||||||
|
interfaces,
|
||||||
|
configured_interfaces: Arc::new(Mutex::new(vec![])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterfaceInfoBuilder {
|
||||||
|
port_location: Option<PortLocation>,
|
||||||
|
interface_type: Option<InterfaceType>,
|
||||||
|
operating_mode: Option<PortOperatingMode>,
|
||||||
|
status: Option<InterfaceStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceInfoBuilder {
|
||||||
|
fn build(&self) -> InterfaceInfo {
|
||||||
|
let interface_type = self
|
||||||
|
.interface_type
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(InterfaceType::Ethernet("TenGigabitEthernet".into()));
|
||||||
|
let port_location = self.port_location.clone().unwrap_or(PortLocation(1, 0, 1));
|
||||||
|
let name = format!("{interface_type} {port_location}");
|
||||||
|
let status = self.status.clone().unwrap_or(InterfaceStatus::Connected);
|
||||||
|
|
||||||
|
InterfaceInfo {
|
||||||
|
name,
|
||||||
|
port_location,
|
||||||
|
interface_type,
|
||||||
|
operating_mode: self.operating_mode.clone(),
|
||||||
|
status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_port_location(self, port_location: PortLocation) -> Self {
|
||||||
|
Self {
|
||||||
|
port_location: Some(port_location),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_operating_mode(self, operating_mode: Option<PortOperatingMode>) -> Self {
|
||||||
|
Self {
|
||||||
|
operating_mode,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_status(self, status: InterfaceStatus) -> Self {
|
||||||
|
Self {
|
||||||
|
status: Some(status),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterSwitchLinkBuilder {
|
||||||
|
link: Option<(PortLocation, PortLocation)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterSwitchLinkBuilder {
|
||||||
|
fn build(&self) -> InterSwitchLink {
|
||||||
|
let link = self
|
||||||
|
.link
|
||||||
|
.clone()
|
||||||
|
.unwrap_or((PortLocation(1, 0, 1), PortLocation(2, 0, 1)));
|
||||||
|
|
||||||
|
InterSwitchLink {
|
||||||
|
local_port: link.0,
|
||||||
|
remote_port: Some(link.1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn between(self, local_port: PortLocation, remote_port: PortLocation) -> Self {
|
||||||
|
Self {
|
||||||
|
link: Some((local_port, remote_port)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn given_interface() -> InterfaceInfoBuilder {
|
||||||
|
InterfaceInfoBuilder {
|
||||||
|
port_location: None,
|
||||||
|
interface_type: None,
|
||||||
|
operating_mode: None,
|
||||||
|
status: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn given_inter_switch_link() -> InterSwitchLinkBuilder {
|
||||||
|
InterSwitchLinkBuilder { link: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user