Compare commits
8 Commits
feat/opnse
...
feat/broca
| Author | SHA1 | Date | |
|---|---|---|---|
| a646f1f4d0 | |||
| 2728fc8989 | |||
| 8c8baaf9cc | |||
| a1c9bfeabd | |||
| d8dab12834 | |||
| 7422534018 | |||
| b67275662d | |||
| 6237e1d877 |
4
brocade/examples/env.sh
Normal file
4
brocade/examples/env.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
export HARMONY_SECRET_NAMESPACE=brocade-example
|
||||
export HARMONY_SECRET_STORE=file
|
||||
export HARMONY_DATABASE_URL=sqlite://harmony_brocade_example.sqlite
|
||||
export RUST_LOG=info
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use brocade::{BrocadeOptions, ssh};
|
||||
use brocade::{BrocadeOptions, Vlan, ssh};
|
||||
use harmony_secret::{Secret, SecretManager};
|
||||
use harmony_types::switch::PortLocation;
|
||||
use schemars::JsonSchema;
|
||||
@@ -17,9 +17,12 @@ async fn main() {
|
||||
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(127, 0, 0, 1)); // brocade @ sto1
|
||||
// let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); // brocade @ sto1
|
||||
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
|
||||
let switch_addresses = vec![ip];
|
||||
//let switch_addresses = vec![ip];
|
||||
let ip0 = IpAddr::V4(Ipv4Addr::new(192, 168, 12, 147)); // brocade @ test
|
||||
let ip1 = IpAddr::V4(Ipv4Addr::new(192, 168, 12, 109)); // brocade @ test
|
||||
let switch_addresses = vec![ip0, ip1];
|
||||
|
||||
let config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||
.await
|
||||
@@ -32,7 +35,7 @@ async fn main() {
|
||||
&BrocadeOptions {
|
||||
dry_run: true,
|
||||
ssh: ssh::SshOptions {
|
||||
port: 2222,
|
||||
port: 22,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -58,12 +61,32 @@ async fn main() {
|
||||
}
|
||||
|
||||
println!("--------------");
|
||||
todo!();
|
||||
println!("Creating VLAN 100 (test-vlan)...");
|
||||
brocade
|
||||
.create_vlan(&Vlan {
|
||||
id: 100,
|
||||
name: "test-vlan".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("--------------");
|
||||
println!("Deleting VLAN 100...");
|
||||
brocade
|
||||
.delete_vlan(&Vlan {
|
||||
id: 100,
|
||||
name: "test-vlan".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("--------------");
|
||||
todo!("STOP!");
|
||||
let channel_name = "1";
|
||||
brocade.clear_port_channel(channel_name).await.unwrap();
|
||||
|
||||
println!("--------------");
|
||||
let channel_id = brocade.find_available_channel_id().await.unwrap();
|
||||
let channel_id = 1;
|
||||
|
||||
println!("--------------");
|
||||
let channel_name = "HARMONY_LAG";
|
||||
|
||||
242
brocade/examples/main_vlan_demo.rs
Normal file
242
brocade/examples/main_vlan_demo.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use brocade::{
|
||||
BrocadeOptions, InterfaceConfig, InterfaceSpeed, InterfaceType, PortOperatingMode,
|
||||
SwitchInterface, Vlan, VlanList, ssh,
|
||||
};
|
||||
use harmony_secret::{Secret, SecretManager};
|
||||
use harmony_types::switch::PortLocation;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
||||
struct BrocadeSwitchAuth {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
fn wait_for_enter() {
|
||||
println!("\n--- Press ENTER to continue ---");
|
||||
io::stdout().flush().unwrap();
|
||||
io::stdin().read_line(&mut String::new()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let ip0 = std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 12, 147));
|
||||
let ip1 = std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 12, 109));
|
||||
let switch_addresses = vec![ip0, ip1];
|
||||
|
||||
let config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let brocade = brocade::init(
|
||||
&switch_addresses,
|
||||
&config.username,
|
||||
&config.password,
|
||||
&BrocadeOptions {
|
||||
dry_run: false,
|
||||
ssh: ssh::SshOptions {
|
||||
port: 22,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("Brocade client failed to connect");
|
||||
|
||||
println!("=== Connecting to Brocade switches ===");
|
||||
let version = brocade.version().await.unwrap();
|
||||
println!("Version: {version:?}");
|
||||
let entries = brocade.get_stack_topology().await.unwrap();
|
||||
println!("Stack topology: {entries:#?}");
|
||||
|
||||
println!("\n=== Creating VLANs 100, 200, 300 ===");
|
||||
brocade
|
||||
.create_vlan(&Vlan {
|
||||
id: 100,
|
||||
name: "vlan100".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Created VLAN 100 (vlan100)");
|
||||
brocade
|
||||
.create_vlan(&Vlan {
|
||||
id: 200,
|
||||
name: "vlan200".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Created VLAN 200 (vlan200)");
|
||||
brocade
|
||||
.create_vlan(&Vlan {
|
||||
id: 300,
|
||||
name: "vlan300".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Created VLAN 300 (vlan300)");
|
||||
|
||||
println!("\n=== Press ENTER to continue to port configuration tests ---");
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEST 1: Trunk port (all VLANs, speed 10Gbps) on TenGigabitEthernet 1/0/1 ===");
|
||||
println!("Configuring port as trunk with all VLANs and speed 10Gbps...");
|
||||
let configs = vec![InterfaceConfig {
|
||||
interface: SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 1),
|
||||
),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: Some(InterfaceSpeed::Gbps10),
|
||||
}];
|
||||
brocade.configure_interfaces(&configs).await.unwrap();
|
||||
println!("Querying interfaces...");
|
||||
let interfaces = brocade.get_interfaces().await.unwrap();
|
||||
for iface in &interfaces {
|
||||
if iface.name.contains("1/0/1") {
|
||||
println!(" {iface:?}");
|
||||
}
|
||||
}
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEST 2: Trunk port (specific VLANs) on TenGigabitEthernet 1/0/2 ===");
|
||||
println!("Configuring port as trunk with VLANs 100, 200...");
|
||||
let configs = vec![InterfaceConfig {
|
||||
interface: SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 2),
|
||||
),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::Specific(vec![
|
||||
Vlan {
|
||||
id: 100,
|
||||
name: "vlan100".to_string(),
|
||||
},
|
||||
Vlan {
|
||||
id: 200,
|
||||
name: "vlan200".to_string(),
|
||||
},
|
||||
])),
|
||||
speed: None,
|
||||
}];
|
||||
brocade.configure_interfaces(&configs).await.unwrap();
|
||||
println!("Querying interfaces...");
|
||||
let interfaces = brocade.get_interfaces().await.unwrap();
|
||||
for iface in &interfaces {
|
||||
if iface.name.contains("1/0/2") {
|
||||
println!(" {iface:?}");
|
||||
}
|
||||
}
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEST 3: Access port (default VLAN 1) on TenGigabitEthernet 1/0/3 ===");
|
||||
println!("Configuring port as access (default VLAN 1)...");
|
||||
let configs = vec![InterfaceConfig {
|
||||
interface: SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 3),
|
||||
),
|
||||
mode: PortOperatingMode::Access,
|
||||
access_vlan: None,
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
}];
|
||||
brocade.configure_interfaces(&configs).await.unwrap();
|
||||
println!("Querying interfaces...");
|
||||
let interfaces = brocade.get_interfaces().await.unwrap();
|
||||
for iface in &interfaces {
|
||||
if iface.name.contains("1/0/3") {
|
||||
println!(" {iface:?}");
|
||||
}
|
||||
}
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEST 4: Access port (custom VLAN 100) on TenGigabitEthernet 1/0/4 ===");
|
||||
println!("Configuring port as access with VLAN 100...");
|
||||
let configs = vec![InterfaceConfig {
|
||||
interface: SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 4),
|
||||
),
|
||||
mode: PortOperatingMode::Access,
|
||||
access_vlan: Some(100),
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
}];
|
||||
brocade.configure_interfaces(&configs).await.unwrap();
|
||||
println!("Querying interfaces...");
|
||||
let interfaces = brocade.get_interfaces().await.unwrap();
|
||||
for iface in &interfaces {
|
||||
if iface.name.contains("1/0/4") {
|
||||
println!(" {iface:?}");
|
||||
}
|
||||
}
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEST 5: Port-channel on TenGigabitEthernet 1/0/5 and 1/0/6 ===");
|
||||
let channel_id = 1;
|
||||
println!("Using channel ID: {channel_id}");
|
||||
println!("Creating port-channel with ports 1/0/5 and 1/0/6...");
|
||||
let ports = [PortLocation(1, 0, 5), PortLocation(1, 0, 6)];
|
||||
brocade
|
||||
.create_port_channel(channel_id, "HARMONY_LAG", &ports)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Port-channel created.");
|
||||
println!("Querying port-channel summary...");
|
||||
let interfaces = brocade.get_interfaces().await.unwrap();
|
||||
for iface in &interfaces {
|
||||
if iface.name.contains("1/0/5") || iface.name.contains("1/0/6") {
|
||||
println!(" {iface:?}");
|
||||
}
|
||||
}
|
||||
wait_for_enter();
|
||||
|
||||
println!("\n=== TEARDOWN: Clearing port-channels and deleting VLANs ===");
|
||||
println!("Clearing port-channel {channel_id}...");
|
||||
brocade
|
||||
.clear_port_channel(&channel_id.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Resetting interfaces...");
|
||||
for port in 1..=6 {
|
||||
let interface = format!("TenGigabitEthernet 1/0/{port}");
|
||||
println!(" Resetting {interface}...");
|
||||
brocade.reset_interface(&interface).await.unwrap();
|
||||
}
|
||||
|
||||
println!("Deleting VLAN 100...");
|
||||
brocade
|
||||
.delete_vlan(&Vlan {
|
||||
id: 100,
|
||||
name: "vlan100".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Deleting VLAN 200...");
|
||||
brocade
|
||||
.delete_vlan(&Vlan {
|
||||
id: 200,
|
||||
name: "vlan200".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Deleting VLAN 300...");
|
||||
brocade
|
||||
.delete_vlan(&Vlan {
|
||||
id: 300,
|
||||
name: "vlan300".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("\n=== DONE ===");
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::BrocadeClient;
|
||||
use crate::{
|
||||
BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, MacAddressEntry,
|
||||
PortChannelId, PortOperatingMode, parse_brocade_mac_address, shell::BrocadeShell,
|
||||
BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceConfig, InterfaceInfo,
|
||||
MacAddressEntry, PortChannelId, PortOperatingMode, Vlan, parse_brocade_mac_address,
|
||||
shell::BrocadeShell,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -138,10 +139,15 @@ impl BrocadeClient for FastIronClient {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
_interfaces: &Vec<(String, PortOperatingMode)>,
|
||||
) -> Result<(), Error> {
|
||||
async fn configure_interfaces(&self, _interfaces: &Vec<InterfaceConfig>) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, _vlan: &Vlan) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, _vlan: &Vlan) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -194,6 +200,25 @@ impl BrocadeClient for FastIronClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_interface(&self, interface: &str) -> Result<(), Error> {
|
||||
info!("[Brocade] Resetting interface: {interface}");
|
||||
|
||||
let commands = vec![
|
||||
"configure terminal".into(),
|
||||
format!("interface {interface}"),
|
||||
"no switchport".into(),
|
||||
"no speed".into(),
|
||||
"exit".into(),
|
||||
];
|
||||
|
||||
self.shell
|
||||
.run_commands(commands, ExecutionMode::Privileged)
|
||||
.await?;
|
||||
|
||||
info!("[Brocade] Interface '{interface}' reset.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
|
||||
info!("[Brocade] Clearing port-channel: {channel_name}");
|
||||
|
||||
|
||||
@@ -76,6 +76,74 @@ pub struct MacAddressEntry {
|
||||
|
||||
pub type PortChannelId = u8;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Vlan {
|
||||
pub id: u16,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub enum VlanList {
|
||||
All,
|
||||
Specific(Vec<Vlan>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub enum SwitchInterface {
|
||||
Ethernet(InterfaceType, PortLocation),
|
||||
PortChannel(PortChannelId),
|
||||
}
|
||||
|
||||
impl fmt::Display for SwitchInterface {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SwitchInterface::Ethernet(itype, loc) => write!(f, "{itype} {loc}"),
|
||||
SwitchInterface::PortChannel(id) => write!(f, "port-channel {id}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub enum InterfaceSpeed {
|
||||
Mbps100,
|
||||
Gbps1,
|
||||
Gbps1Auto,
|
||||
Gbps10,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl fmt::Display for InterfaceSpeed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InterfaceSpeed::Mbps100 => write!(f, "100"),
|
||||
InterfaceSpeed::Gbps1 => write!(f, "1000"),
|
||||
InterfaceSpeed::Gbps1Auto => write!(f, "1000-auto"),
|
||||
InterfaceSpeed::Gbps10 => write!(f, "10000"),
|
||||
InterfaceSpeed::Auto => write!(f, "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct InterfaceConfig {
|
||||
pub interface: SwitchInterface,
|
||||
pub mode: PortOperatingMode,
|
||||
pub access_vlan: Option<u16>,
|
||||
pub trunk_vlans: Option<VlanList>,
|
||||
pub speed: Option<InterfaceSpeed>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct PortChannelConfig {
|
||||
pub id: PortChannelId,
|
||||
pub name: String,
|
||||
pub ports: Vec<PortLocation>,
|
||||
pub mode: PortOperatingMode,
|
||||
pub access_vlan: Option<Vlan>,
|
||||
pub trunk_vlans: Option<VlanList>,
|
||||
pub speed: Option<InterfaceSpeed>,
|
||||
}
|
||||
|
||||
/// Represents a single physical or logical link connecting two switches within a stack or fabric.
|
||||
///
|
||||
/// This structure provides a standardized view of the topology regardless of the
|
||||
@@ -104,16 +172,17 @@ pub struct InterfaceInfo {
|
||||
}
|
||||
|
||||
/// Categorizes the functional type of a switch interface.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
||||
pub enum InterfaceType {
|
||||
/// Physical or virtual Ethernet interface (e.g., TenGigabitEthernet, FortyGigabitEthernet).
|
||||
Ethernet(String),
|
||||
TenGigabitEthernet,
|
||||
FortyGigabitEthernet,
|
||||
}
|
||||
|
||||
impl fmt::Display for InterfaceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InterfaceType::Ethernet(name) => write!(f, "{name}"),
|
||||
InterfaceType::TenGigabitEthernet => write!(f, "TenGigabitEthernet"),
|
||||
InterfaceType::FortyGigabitEthernet => write!(f, "FortyGigabitEthernet"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,10 +275,13 @@ pub trait BrocadeClient: std::fmt::Debug {
|
||||
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>;
|
||||
async fn configure_interfaces(&self, interfaces: &Vec<InterfaceConfig>) -> Result<(), Error>;
|
||||
|
||||
/// Creates a new VLAN on the switch.
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), Error>;
|
||||
|
||||
/// Deletes a VLAN from the switch.
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), Error>;
|
||||
|
||||
/// Scans the existing configuration to find the next available (unused)
|
||||
/// Port-Channel ID (`lag` or `trunk`) for assignment.
|
||||
@@ -246,6 +318,9 @@ pub trait BrocadeClient: std::fmt::Debug {
|
||||
/// * `des`: The Data Encryption Standard algorithm key
|
||||
async fn enable_snmp(&self, user_name: &str, auth: &str, des: &str) -> Result<(), Error>;
|
||||
|
||||
/// Resets an interface to its default state by removing switchport configuration.
|
||||
async fn reset_interface(&self, interface: &str) -> Result<(), Error>;
|
||||
|
||||
/// Removes all configuration associated with the specified Port-Channel name.
|
||||
///
|
||||
/// This operation should be idempotent; attempting to clear a non-existent
|
||||
|
||||
@@ -6,9 +6,10 @@ use log::{debug, info};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo,
|
||||
InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
|
||||
parse_brocade_mac_address, shell::BrocadeShell,
|
||||
BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceConfig,
|
||||
InterfaceInfo, InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId,
|
||||
PortOperatingMode, SwitchInterface, Vlan, VlanList, parse_brocade_mac_address,
|
||||
shell::BrocadeShell,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -84,8 +85,8 @@ impl NetworkOperatingSystemClient {
|
||||
}
|
||||
|
||||
let interface_type = match parts[0] {
|
||||
"Fo" => InterfaceType::Ethernet("FortyGigabitEthernet".to_string()),
|
||||
"Te" => InterfaceType::Ethernet("TenGigabitEthernet".to_string()),
|
||||
"Fo" => InterfaceType::FortyGigabitEthernet,
|
||||
"Te" => InterfaceType::TenGigabitEthernet,
|
||||
_ => return None,
|
||||
};
|
||||
let port_location = PortLocation::from_str(parts[1]).ok()?;
|
||||
@@ -185,18 +186,20 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<(String, PortOperatingMode)>,
|
||||
) -> Result<(), Error> {
|
||||
async fn configure_interfaces(&self, interfaces: &Vec<InterfaceConfig>) -> Result<(), Error> {
|
||||
info!("[Brocade] Configuring {} interface(s)...", interfaces.len());
|
||||
|
||||
let mut commands = vec!["configure terminal".to_string()];
|
||||
|
||||
for interface in interfaces {
|
||||
commands.push(format!("interface {}", interface.0));
|
||||
debug!(
|
||||
"[Brocade] Configuring interface {} as {:?}",
|
||||
interface.interface, interface.mode
|
||||
);
|
||||
|
||||
match interface.1 {
|
||||
commands.push(format!("interface {}", interface.interface));
|
||||
|
||||
match interface.mode {
|
||||
PortOperatingMode::Fabric => {
|
||||
commands.push("fabric isl enable".into());
|
||||
commands.push("fabric trunk enable".into());
|
||||
@@ -204,23 +207,50 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
||||
PortOperatingMode::Trunk => {
|
||||
commands.push("switchport".into());
|
||||
commands.push("switchport mode trunk".into());
|
||||
commands.push("switchport trunk allowed vlan all".into());
|
||||
match &interface.trunk_vlans {
|
||||
Some(VlanList::All) => {
|
||||
commands.push("switchport trunk allowed vlan all".into());
|
||||
}
|
||||
Some(VlanList::Specific(vlans)) => {
|
||||
for vlan in vlans {
|
||||
commands.push(format!("switchport trunk allowed vlan add {}", vlan.id));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
commands.push("switchport trunk allowed vlan all".into());
|
||||
}
|
||||
}
|
||||
commands.push("no switchport trunk tag native-vlan".into());
|
||||
commands.push("spanning-tree shutdown".into());
|
||||
commands.push("no fabric isl enable".into());
|
||||
commands.push("no fabric trunk enable".into());
|
||||
commands.push("no shutdown".into());
|
||||
if matches!(interface.interface, SwitchInterface::Ethernet(..)) {
|
||||
commands.push("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());
|
||||
let access_vlan = interface.access_vlan.unwrap_or(1);
|
||||
commands.push(format!("switchport access vlan {access_vlan}"));
|
||||
if matches!(interface.interface, SwitchInterface::Ethernet(..)) {
|
||||
commands.push("no spanning-tree shutdown".into());
|
||||
commands.push("no fabric isl enable".into());
|
||||
commands.push("no fabric trunk enable".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(speed) = &interface.speed {
|
||||
info!(
|
||||
"[Brocade] Overriding speed on {} to {speed}",
|
||||
interface.interface
|
||||
);
|
||||
if matches!(interface.interface, SwitchInterface::PortChannel(..)) {
|
||||
commands.push("shutdown".into());
|
||||
}
|
||||
commands.push(format!("speed {speed}"));
|
||||
}
|
||||
|
||||
commands.push("no shutdown".into());
|
||||
commands.push("exit".into());
|
||||
}
|
||||
@@ -235,6 +265,40 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), Error> {
|
||||
info!("[Brocade] Creating VLAN {} ({})", vlan.id, vlan.name);
|
||||
|
||||
let commands = vec![
|
||||
"configure terminal".into(),
|
||||
format!("interface Vlan {}", vlan.id),
|
||||
format!("name {}", vlan.name),
|
||||
"exit".into(),
|
||||
];
|
||||
|
||||
self.shell
|
||||
.run_commands(commands, ExecutionMode::Regular)
|
||||
.await?;
|
||||
|
||||
info!("[Brocade] VLAN {} ({}) created.", vlan.id, vlan.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), Error> {
|
||||
info!("[Brocade] Deleting VLAN {}", vlan.id);
|
||||
|
||||
let commands = vec![
|
||||
"configure terminal".into(),
|
||||
format!("no interface Vlan {}", vlan.id),
|
||||
];
|
||||
|
||||
self.shell
|
||||
.run_commands(commands, ExecutionMode::Regular)
|
||||
.await?;
|
||||
|
||||
info!("[Brocade] VLAN {} deleted.", vlan.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
||||
info!("[Brocade] Finding next available channel id...");
|
||||
|
||||
@@ -283,22 +347,20 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
let interfaces = self.get_interfaces().await?;
|
||||
|
||||
let mut commands = vec![
|
||||
"configure terminal".into(),
|
||||
format!("interface port-channel {}", channel_id),
|
||||
"no shutdown".into(),
|
||||
format!("description {channel_name}"),
|
||||
"exit".into(),
|
||||
];
|
||||
|
||||
for port in ports {
|
||||
let interface = interfaces.iter().find(|i| i.port_location == *port);
|
||||
let Some(interface) = interface else {
|
||||
continue;
|
||||
};
|
||||
|
||||
commands.push(format!("interface {}", interface.name));
|
||||
debug!(
|
||||
"[Brocade] Adding port TenGigabitEthernet {} to channel-group {}",
|
||||
port, channel_id
|
||||
);
|
||||
commands.push(format!("interface TenGigabitEthernet {}", port));
|
||||
commands.push("no switchport".into());
|
||||
commands.push("no ip address".into());
|
||||
commands.push("no fabric isl enable".into());
|
||||
@@ -317,6 +379,25 @@ impl BrocadeClient for NetworkOperatingSystemClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_interface(&self, interface: &str) -> Result<(), Error> {
|
||||
info!("[Brocade] Resetting interface: {interface}");
|
||||
|
||||
let commands = vec![
|
||||
"configure terminal".into(),
|
||||
format!("interface {interface}"),
|
||||
"no switchport".into(),
|
||||
"no speed".into(),
|
||||
"exit".into(),
|
||||
];
|
||||
|
||||
self.shell
|
||||
.run_commands(commands, ExecutionMode::Regular)
|
||||
.await?;
|
||||
|
||||
info!("[Brocade] Interface '{interface}' reset.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_port_channel(&self, channel_name: &str) -> Result<(), Error> {
|
||||
info!("[Brocade] Clearing port-channel: {channel_name}");
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
rustc --version
|
||||
cargo check --all-targets --all-features --keep-going
|
||||
cargo fmt --check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brocade::{BrocadeOptions, PortOperatingMode};
|
||||
use brocade::{BrocadeOptions, InterfaceConfig, InterfaceType, PortOperatingMode, SwitchInterface, VlanList};
|
||||
use harmony::{
|
||||
infra::brocade::BrocadeSwitchConfig,
|
||||
inventory::Inventory,
|
||||
@@ -9,6 +9,13 @@ use harmony::{
|
||||
use harmony_macros::ip;
|
||||
use harmony_types::{id::Id, switch::PortLocation};
|
||||
|
||||
fn tengig(stack: u8, slot: u8, port: u8) -> SwitchInterface {
|
||||
SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(stack, slot, port),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_switch_config() -> BrocadeSwitchConfig {
|
||||
let mut options = BrocadeOptions::default();
|
||||
options.ssh.port = 2222;
|
||||
@@ -33,9 +40,27 @@ async fn main() {
|
||||
Id::from_str("18").unwrap(),
|
||||
],
|
||||
ports_to_configure: vec![
|
||||
(PortLocation(2, 0, 17), PortOperatingMode::Trunk),
|
||||
(PortLocation(2, 0, 19), PortOperatingMode::Trunk),
|
||||
(PortLocation(1, 0, 18), PortOperatingMode::Trunk),
|
||||
InterfaceConfig {
|
||||
interface: tengig(2, 0, 17),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: None,
|
||||
},
|
||||
InterfaceConfig {
|
||||
interface: tengig(2, 0, 19),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: None,
|
||||
},
|
||||
InterfaceConfig {
|
||||
interface: tengig(1, 0, 18),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
18
examples/brocade_switch_configuration/Cargo.toml
Normal file
18
examples/brocade_switch_configuration/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "brocade-switch-configuration"
|
||||
edition = "2024"
|
||||
version.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
harmony = { path = "../../harmony" }
|
||||
harmony_cli = { path = "../../harmony_cli" }
|
||||
harmony_macros = { path = "../../harmony_macros" }
|
||||
harmony_types = { path = "../../harmony_types" }
|
||||
tokio.workspace = true
|
||||
async-trait.workspace = true
|
||||
serde.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
brocade = { path = "../../brocade" }
|
||||
4
examples/brocade_switch_configuration/env.sh
Normal file
4
examples/brocade_switch_configuration/env.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
export HARMONY_SECRET_NAMESPACE=brocade-example
|
||||
export HARMONY_SECRET_STORE=file
|
||||
export HARMONY_DATABASE_URL=sqlite://harmony_brocade_example.sqlite
|
||||
export RUST_LOG=info
|
||||
143
examples/brocade_switch_configuration/src/main.rs
Normal file
143
examples/brocade_switch_configuration/src/main.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use brocade::{
|
||||
BrocadeOptions, InterfaceConfig, InterfaceSpeed, InterfaceType, PortChannelConfig,
|
||||
PortOperatingMode, SwitchInterface, Vlan, VlanList,
|
||||
};
|
||||
use harmony::{
|
||||
infra::brocade::BrocadeSwitchConfig,
|
||||
inventory::Inventory,
|
||||
modules::brocade::{BrocadeSwitchAuth, BrocadeSwitchConfigurationScore, SwitchTopology},
|
||||
};
|
||||
use harmony_macros::ip;
|
||||
use harmony_types::switch::PortLocation;
|
||||
|
||||
fn tengig(stack: u8, slot: u8, port: u8) -> SwitchInterface {
|
||||
SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(stack, slot, port),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_switch_config() -> BrocadeSwitchConfig {
|
||||
let auth = BrocadeSwitchAuth {
|
||||
username: "admin".to_string(),
|
||||
password: "password".to_string(),
|
||||
};
|
||||
|
||||
BrocadeSwitchConfig {
|
||||
ips: vec![ip!("192.168.12.147"), ip!("192.168.12.109")],
|
||||
auth,
|
||||
options: BrocadeOptions {
|
||||
dry_run: false,
|
||||
ssh: brocade::ssh::SshOptions {
|
||||
port: 22,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
harmony_cli::cli_logger::init();
|
||||
|
||||
// ===================================================
|
||||
// Step 1: Define VLANs once, use them everywhere
|
||||
// ===================================================
|
||||
let mgmt = Vlan {
|
||||
id: 100,
|
||||
name: "MGMT".to_string(),
|
||||
};
|
||||
let data = Vlan {
|
||||
id: 200,
|
||||
name: "DATA".to_string(),
|
||||
};
|
||||
let storage = Vlan {
|
||||
id: 300,
|
||||
name: "STORAGE".to_string(),
|
||||
};
|
||||
let backup = Vlan {
|
||||
id: 400,
|
||||
name: "BACKUP".to_string(),
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
// Step 2: Build the score
|
||||
// ===================================================
|
||||
let score = BrocadeSwitchConfigurationScore {
|
||||
// All VLANs that need to exist on the switch
|
||||
vlans: vec![mgmt.clone(), data.clone(), storage.clone(), backup.clone()],
|
||||
|
||||
// Standalone interfaces (not part of any port-channel)
|
||||
interfaces: vec![
|
||||
// Trunk port with ALL VLANs, forced to 10Gbps
|
||||
InterfaceConfig {
|
||||
interface: tengig(1, 0, 1),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: Some(InterfaceSpeed::Gbps10),
|
||||
},
|
||||
// Trunk port with specific VLANs (MGMT + DATA only)
|
||||
InterfaceConfig {
|
||||
interface: tengig(1, 0, 2),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::Specific(vec![mgmt.clone(), data.clone()])),
|
||||
speed: None,
|
||||
},
|
||||
// Access port on the MGMT VLAN
|
||||
InterfaceConfig {
|
||||
interface: tengig(1, 0, 3),
|
||||
mode: PortOperatingMode::Access,
|
||||
access_vlan: Some(mgmt.id),
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
},
|
||||
// Access port on the STORAGE VLAN
|
||||
InterfaceConfig {
|
||||
interface: tengig(1, 0, 4),
|
||||
mode: PortOperatingMode::Access,
|
||||
access_vlan: Some(storage.id),
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
},
|
||||
],
|
||||
|
||||
// Port-channels: member ports are bundled, L2 config goes on the port-channel
|
||||
port_channels: vec![
|
||||
// Port-channel 1: trunk with DATA + STORAGE VLANs, forced to 1Gbps
|
||||
PortChannelConfig {
|
||||
id: 1,
|
||||
name: "SERVER_BOND".to_string(),
|
||||
ports: vec![PortLocation(1, 0, 5), PortLocation(1, 0, 6)],
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::Specific(vec![data.clone(), storage.clone()])),
|
||||
speed: Some(InterfaceSpeed::Gbps1),
|
||||
},
|
||||
// Port-channel 2: trunk with all VLANs, default speed
|
||||
PortChannelConfig {
|
||||
id: 2,
|
||||
name: "BACKUP_BOND".to_string(),
|
||||
ports: vec![PortLocation(1, 0, 7), PortLocation(1, 0, 8)],
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: Some(VlanList::All),
|
||||
speed: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ===================================================
|
||||
// Step 3: Run
|
||||
// ===================================================
|
||||
harmony_cli::run(
|
||||
Inventory::autoload(),
|
||||
SwitchTopology::new(get_switch_config()).await,
|
||||
vec![Box::new(score)],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use brocade::{InterfaceConfig, PortChannelConfig, PortChannelId, Vlan};
|
||||
use harmony_k8s::K8sClient;
|
||||
use harmony_macros::ip;
|
||||
use harmony_types::{
|
||||
@@ -11,7 +12,7 @@ use log::info;
|
||||
|
||||
use crate::topology::{HelmCommand, PxeOptions};
|
||||
use crate::{data::FileContent, executors::ExecutorError, topology::node_exporter::NodeExporter};
|
||||
use crate::{infra::network_manager::OpenShiftNmStateNetworkManager, topology::PortConfig};
|
||||
use crate::infra::network_manager::OpenShiftNmStateNetworkManager;
|
||||
|
||||
use super::{
|
||||
DHCPStaticEntry, DhcpServer, DnsRecord, DnsRecordType, DnsServer, Firewall, HostNetworkConfig,
|
||||
@@ -316,23 +317,51 @@ impl Switch for HAClusterTopology {
|
||||
self.switch_client.find_port(mac_address).await
|
||||
}
|
||||
|
||||
async fn configure_port_channel(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
channel_id: PortChannelId,
|
||||
config: &HostNetworkConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
debug!("Configuring port channel: {config:#?}");
|
||||
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
||||
|
||||
self.switch_client
|
||||
.configure_port_channel(&format!("Harmony_{}", config.host_id), switch_ports)
|
||||
.configure_port_channel(
|
||||
channel_id,
|
||||
&format!("Harmony_{}", config.host_id),
|
||||
switch_ports,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(format!("Failed to configure port-channel: {e}")))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn configure_port_channel_from_config(
|
||||
&self,
|
||||
config: &PortChannelConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
self.switch_client
|
||||
.configure_port_channel(config.id, &config.name, config.ports.clone())
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(format!("Failed to create port-channel: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_port_channel(&self, _ids: &Vec<Id>) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn configure_interface(&self, _ports: &Vec<PortConfig>) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
self.switch_client.configure_interfaces(interfaces).await
|
||||
}
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.switch_client.create_vlan(vlan).await
|
||||
}
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.switch_client.delete_vlan(vlan).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,6 +621,7 @@ impl SwitchClient for DummyInfra {
|
||||
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
_channel_id: PortChannelId,
|
||||
_channel_name: &str,
|
||||
_switch_ports: Vec<PortLocation>,
|
||||
) -> Result<u8, SwitchError> {
|
||||
@@ -600,7 +630,16 @@ impl SwitchClient for DummyInfra {
|
||||
async fn clear_port_channel(&self, _ids: &Vec<Id>) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn configure_interface(&self, _ports: &Vec<PortConfig>) -> Result<(), SwitchError> {
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
_interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn create_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn delete_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use brocade::PortOperatingMode;
|
||||
use brocade::{InterfaceConfig, PortChannelConfig, PortChannelId, Vlan};
|
||||
use derive_new::new;
|
||||
use harmony_k8s::K8sClient;
|
||||
use harmony_types::{
|
||||
@@ -220,8 +220,6 @@ impl From<String> for NetworkError {
|
||||
}
|
||||
}
|
||||
|
||||
pub type PortConfig = (PortLocation, PortOperatingMode);
|
||||
|
||||
#[async_trait]
|
||||
pub trait Switch: Send + Sync {
|
||||
async fn setup_switch(&self) -> Result<(), SwitchError>;
|
||||
@@ -231,9 +229,24 @@ pub trait Switch: Send + Sync {
|
||||
mac_address: &MacAddress,
|
||||
) -> Result<Option<PortLocation>, SwitchError>;
|
||||
|
||||
async fn configure_port_channel(&self, config: &HostNetworkConfig) -> Result<(), SwitchError>;
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
channel_id: PortChannelId,
|
||||
config: &HostNetworkConfig,
|
||||
) -> Result<(), SwitchError>;
|
||||
/// Creates a port-channel from a PortChannelConfig (id, name, member ports).
|
||||
/// Does NOT configure L2 mode — use configure_interfaces for that.
|
||||
async fn configure_port_channel_from_config(
|
||||
&self,
|
||||
config: &PortChannelConfig,
|
||||
) -> Result<(), SwitchError>;
|
||||
async fn clear_port_channel(&self, ids: &Vec<Id>) -> Result<(), SwitchError>;
|
||||
async fn configure_interface(&self, ports: &Vec<PortConfig>) -> Result<(), SwitchError>;
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError>;
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError>;
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -290,12 +303,18 @@ pub trait SwitchClient: Debug + Send + Sync {
|
||||
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
channel_id: PortChannelId,
|
||||
channel_name: &str,
|
||||
switch_ports: Vec<PortLocation>,
|
||||
) -> Result<u8, SwitchError>;
|
||||
|
||||
async fn clear_port_channel(&self, ids: &Vec<Id>) -> Result<(), SwitchError>;
|
||||
async fn configure_interface(&self, ports: &Vec<PortConfig>) -> Result<(), SwitchError>;
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError>;
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError>;
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use async_trait::async_trait;
|
||||
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
||||
use brocade::{
|
||||
BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceConfig, InterfaceStatus,
|
||||
PortChannelId, PortOperatingMode, Vlan,
|
||||
};
|
||||
|
||||
use harmony_types::{
|
||||
id::Id,
|
||||
net::{IpAddress, MacAddress},
|
||||
switch::{PortDeclaration, PortLocation},
|
||||
};
|
||||
use log::{info, warn};
|
||||
use log::info;
|
||||
use option_ext::OptionExt;
|
||||
|
||||
use crate::{
|
||||
modules::brocade::BrocadeSwitchAuth,
|
||||
topology::{PortConfig, SwitchClient, SwitchError},
|
||||
topology::{SwitchClient, SwitchError},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -54,7 +58,7 @@ impl SwitchClient for BrocadeSwitchClient {
|
||||
|
||||
info!("Brocade found interfaces {interfaces:#?}");
|
||||
|
||||
let interfaces: Vec<(String, PortOperatingMode)> = interfaces
|
||||
let interfaces: Vec<InterfaceConfig> = interfaces
|
||||
.into_iter()
|
||||
.filter(|interface| {
|
||||
interface.operating_mode.is_none() && interface.status == InterfaceStatus::Connected
|
||||
@@ -65,7 +69,16 @@ impl SwitchClient for BrocadeSwitchClient {
|
||||
|| link.remote_port.contains(&interface.port_location)
|
||||
})
|
||||
})
|
||||
.map(|interface| (interface.name.clone(), PortOperatingMode::Trunk))
|
||||
.map(|interface| InterfaceConfig {
|
||||
interface: brocade::SwitchInterface::Ethernet(
|
||||
interface.interface_type.clone(),
|
||||
interface.port_location.clone(),
|
||||
),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if interfaces.is_empty() {
|
||||
@@ -114,50 +127,18 @@ impl SwitchClient for BrocadeSwitchClient {
|
||||
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
channel_id: PortChannelId,
|
||||
channel_name: &str,
|
||||
switch_ports: Vec<PortLocation>,
|
||||
) -> Result<u8, SwitchError> {
|
||||
let mut channel_id = self
|
||||
.brocade
|
||||
.find_available_channel_id()
|
||||
self.brocade
|
||||
.create_port_channel(channel_id, channel_name, &switch_ports)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(format!("{e}")))?;
|
||||
|
||||
info!("Found next available channel id : {channel_id}");
|
||||
|
||||
loop {
|
||||
match self
|
||||
.brocade
|
||||
.create_port_channel(channel_id, channel_name, &switch_ports)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(format!("{e}")))
|
||||
{
|
||||
Ok(_) => {
|
||||
info!(
|
||||
"Successfully configured port channel {channel_id} {channel_name} for ports {switch_ports:?}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Could not configure port channel {channel_id} {channel_name} for ports {switch_ports:?}"
|
||||
);
|
||||
let previous_id = channel_id;
|
||||
|
||||
while previous_id == channel_id {
|
||||
channel_id = inquire::Text::new(
|
||||
"Type the port channel number to use (or CTRL+C to exit) :",
|
||||
)
|
||||
.prompt()
|
||||
.map_err(|e| {
|
||||
SwitchError::new(format!("Failed to prompt for channel id : {e}"))
|
||||
})?
|
||||
.parse()
|
||||
.unwrap_or(channel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"Successfully configured port channel {channel_id} {channel_name} for ports {switch_ports:?}"
|
||||
);
|
||||
|
||||
Ok(channel_id)
|
||||
}
|
||||
@@ -170,14 +151,28 @@ impl SwitchClient for BrocadeSwitchClient {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn configure_interface(&self, ports: &Vec<PortConfig>) -> Result<(), SwitchError> {
|
||||
// FIXME hardcoded TenGigabitEthernet = bad
|
||||
let ports = ports
|
||||
.iter()
|
||||
.map(|p| (format!("TenGigabitEthernet {}", p.0), p.1.clone()))
|
||||
.collect();
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
self.brocade
|
||||
.configure_interfaces(&ports)
|
||||
.configure_interfaces(interfaces)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.brocade
|
||||
.create_vlan(vlan)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.brocade
|
||||
.delete_vlan(vlan)
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(e.to_string()))?;
|
||||
Ok(())
|
||||
@@ -208,8 +203,9 @@ impl SwitchClient for UnmanagedSwitch {
|
||||
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
channel_name: &str,
|
||||
switch_ports: Vec<PortLocation>,
|
||||
_channel_id: PortChannelId,
|
||||
_channel_name: &str,
|
||||
_switch_ports: Vec<PortLocation>,
|
||||
) -> Result<u8, SwitchError> {
|
||||
todo!("unmanaged switch. Nothing to do.")
|
||||
}
|
||||
@@ -217,8 +213,19 @@ impl SwitchClient for UnmanagedSwitch {
|
||||
async fn clear_port_channel(&self, ids: &Vec<Id>) -> Result<(), SwitchError> {
|
||||
todo!("unmanged switch. Nothing to do.")
|
||||
}
|
||||
async fn configure_interface(&self, ports: &Vec<PortConfig>) -> Result<(), SwitchError> {
|
||||
todo!("unmanged switch. Nothing to do.")
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
_interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
todo!("unmanaged switch. Nothing to do.")
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!("unmanaged switch. Nothing to do.")
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!("unmanaged switch. Nothing to do.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,8 +236,9 @@ mod tests {
|
||||
use assertor::*;
|
||||
use async_trait::async_trait;
|
||||
use brocade::{
|
||||
BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceInfo, InterfaceStatus,
|
||||
InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode, SecurityLevel,
|
||||
BrocadeClient, BrocadeInfo, Error, InterSwitchLink, InterfaceConfig, InterfaceInfo,
|
||||
InterfaceStatus, InterfaceType, MacAddressEntry, PortChannelId, PortOperatingMode,
|
||||
SecurityLevel, Vlan,
|
||||
};
|
||||
use harmony_types::switch::PortLocation;
|
||||
|
||||
@@ -258,8 +266,26 @@ mod tests {
|
||||
//TODO not sure about this
|
||||
let configured_interfaces = brocade.configured_interfaces.lock().unwrap();
|
||||
assert_that!(*configured_interfaces).contains_exactly(vec![
|
||||
(first_interface.name.clone(), PortOperatingMode::Trunk),
|
||||
(second_interface.name.clone(), PortOperatingMode::Trunk),
|
||||
InterfaceConfig {
|
||||
interface: brocade::SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 1),
|
||||
),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
},
|
||||
InterfaceConfig {
|
||||
interface: brocade::SwitchInterface::Ethernet(
|
||||
InterfaceType::TenGigabitEthernet,
|
||||
PortLocation(1, 0, 4),
|
||||
),
|
||||
mode: PortOperatingMode::Trunk,
|
||||
access_vlan: None,
|
||||
trunk_vlans: None,
|
||||
speed: None,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -343,7 +369,7 @@ mod tests {
|
||||
struct FakeBrocadeClient {
|
||||
stack_topology: Vec<InterSwitchLink>,
|
||||
interfaces: Vec<InterfaceInfo>,
|
||||
configured_interfaces: Arc<Mutex<Vec<(String, PortOperatingMode)>>>,
|
||||
configured_interfaces: Arc<Mutex<Vec<InterfaceConfig>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -366,7 +392,7 @@ mod tests {
|
||||
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<(String, PortOperatingMode)>,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), Error> {
|
||||
let mut configured_interfaces = self.configured_interfaces.lock().unwrap();
|
||||
*configured_interfaces = interfaces.clone();
|
||||
@@ -374,6 +400,14 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, _vlan: &Vlan) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, _vlan: &Vlan) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn find_available_channel_id(&self) -> Result<PortChannelId, Error> {
|
||||
todo!()
|
||||
}
|
||||
@@ -387,6 +421,10 @@ mod tests {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn reset_interface(&self, _interface: &str) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn clear_port_channel(&self, _channel_name: &str) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
@@ -418,7 +456,7 @@ mod tests {
|
||||
let interface_type = self
|
||||
.interface_type
|
||||
.clone()
|
||||
.unwrap_or(InterfaceType::Ethernet("TenGigabitEthernet".into()));
|
||||
.unwrap_or(InterfaceType::TenGigabitEthernet);
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use brocade::{BrocadeOptions, PortOperatingMode};
|
||||
use brocade::{BrocadeOptions, InterfaceConfig, PortChannelConfig, PortChannelId, PortOperatingMode, Vlan};
|
||||
|
||||
use crate::{
|
||||
data::Version,
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::{
|
||||
HostNetworkConfig, PortConfig, PreparationError, PreparationOutcome, Switch, SwitchClient,
|
||||
HostNetworkConfig, PreparationError, PreparationOutcome, Switch, SwitchClient,
|
||||
SwitchError, Topology,
|
||||
},
|
||||
};
|
||||
@@ -20,7 +20,7 @@ use serde::Serialize;
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct BrocadeSwitchScore {
|
||||
pub port_channels_to_clear: Vec<Id>,
|
||||
pub ports_to_configure: Vec<PortConfig>,
|
||||
pub ports_to_configure: Vec<InterfaceConfig>,
|
||||
}
|
||||
|
||||
impl<T: Topology + Switch> Score<T> for BrocadeSwitchScore {
|
||||
@@ -59,7 +59,7 @@ impl<T: Topology + Switch> Interpret<T> for BrocadeSwitchInterpret {
|
||||
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||
debug!("Configuring interfaces {:?}", self.score.ports_to_configure);
|
||||
topology
|
||||
.configure_interface(&self.score.ports_to_configure)
|
||||
.configure_interfaces(&self.score.ports_to_configure)
|
||||
.await
|
||||
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||
Ok(Outcome::success("switch configured".to_string()))
|
||||
@@ -126,13 +126,38 @@ impl Switch for SwitchTopology {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn configure_port_channel(&self, _config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
_channel_id: PortChannelId,
|
||||
_config: &HostNetworkConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn configure_port_channel_from_config(
|
||||
&self,
|
||||
config: &PortChannelConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
self.client
|
||||
.configure_port_channel(config.id, &config.name, config.ports.clone())
|
||||
.await
|
||||
.map_err(|e| SwitchError::new(format!("Failed to create port-channel: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
async fn clear_port_channel(&self, ids: &Vec<Id>) -> Result<(), SwitchError> {
|
||||
self.client.clear_port_channel(ids).await
|
||||
}
|
||||
async fn configure_interface(&self, ports: &Vec<PortConfig>) -> Result<(), SwitchError> {
|
||||
self.client.configure_interface(ports).await
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
self.client.configure_interfaces(interfaces).await
|
||||
}
|
||||
|
||||
async fn create_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.client.create_vlan(vlan).await
|
||||
}
|
||||
|
||||
async fn delete_vlan(&self, vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
self.client.delete_vlan(vlan).await
|
||||
}
|
||||
}
|
||||
|
||||
179
harmony/src/modules/brocade/brocade_switch_configuration.rs
Normal file
179
harmony/src/modules/brocade/brocade_switch_configuration.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use async_trait::async_trait;
|
||||
use brocade::{InterfaceConfig, PortChannelConfig, Vlan};
|
||||
use harmony_types::id::Id;
|
||||
use log::{debug, info};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
data::Version,
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::{Switch, SwitchError, Topology},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct BrocadeSwitchConfigurationScore {
|
||||
/// VLANs to create on the switch. Define once, reference everywhere.
|
||||
pub vlans: Vec<Vlan>,
|
||||
/// Standalone interfaces (NOT members of a port-channel).
|
||||
/// Each has its own VLAN/mode configuration.
|
||||
pub interfaces: Vec<InterfaceConfig>,
|
||||
/// Port-channels: bundles of ports with VLAN/mode config
|
||||
/// applied on the logical port-channel interface, not on the members.
|
||||
pub port_channels: Vec<PortChannelConfig>,
|
||||
}
|
||||
|
||||
impl<T: Topology + Switch> Score<T> for BrocadeSwitchConfigurationScore {
|
||||
fn name(&self) -> String {
|
||||
"BrocadeSwitchConfigurationScore".to_string()
|
||||
}
|
||||
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
Box::new(BrocadeSwitchConfigurationInterpret {
|
||||
score: self.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BrocadeSwitchConfigurationInterpret {
|
||||
score: BrocadeSwitchConfigurationScore,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology + Switch> Interpret<T> for BrocadeSwitchConfigurationInterpret {
|
||||
async fn execute(
|
||||
&self,
|
||||
_inventory: &Inventory,
|
||||
topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
self.create_vlans(topology).await?;
|
||||
self.create_port_channels(topology).await?;
|
||||
self.configure_port_channel_interfaces(topology).await?;
|
||||
self.configure_standalone_interfaces(topology).await?;
|
||||
|
||||
Ok(Outcome::success(
|
||||
"Switch configuration applied successfully".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_name(&self) -> InterpretName {
|
||||
InterpretName::Custom("BrocadeSwitchConfigurationInterpret")
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Version {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_status(&self) -> InterpretStatus {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_children(&self) -> Vec<Id> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl BrocadeSwitchConfigurationInterpret {
|
||||
async fn create_vlans<T: Topology + Switch>(
|
||||
&self,
|
||||
topology: &T,
|
||||
) -> Result<(), InterpretError> {
|
||||
for vlan in &self.score.vlans {
|
||||
info!("Creating VLAN {} ({})", vlan.id, vlan.name);
|
||||
topology
|
||||
.create_vlan(vlan)
|
||||
.await
|
||||
.map_err(|e| InterpretError::new(format!("Failed to create VLAN {}: {e}", vlan.id)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_port_channels<T: Topology + Switch>(
|
||||
&self,
|
||||
topology: &T,
|
||||
) -> Result<(), InterpretError> {
|
||||
for pc in &self.score.port_channels {
|
||||
info!(
|
||||
"Creating port-channel {} ({}) with ports: {:?}",
|
||||
pc.id, pc.name, pc.ports
|
||||
);
|
||||
topology
|
||||
.configure_port_channel_from_config(pc)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
InterpretError::new(format!(
|
||||
"Failed to create port-channel {} ({}): {e}",
|
||||
pc.id, pc.name
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn configure_port_channel_interfaces<T: Topology + Switch>(
|
||||
&self,
|
||||
topology: &T,
|
||||
) -> Result<(), InterpretError> {
|
||||
let pc_interfaces: Vec<InterfaceConfig> = self
|
||||
.score
|
||||
.port_channels
|
||||
.iter()
|
||||
.map(|pc| InterfaceConfig {
|
||||
interface: brocade::SwitchInterface::PortChannel(pc.id),
|
||||
mode: pc.mode.clone(),
|
||||
access_vlan: pc.access_vlan.as_ref().map(|v| v.id),
|
||||
trunk_vlans: pc.trunk_vlans.clone(),
|
||||
speed: pc.speed.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !pc_interfaces.is_empty() {
|
||||
info!(
|
||||
"Configuring L2 mode on {} port-channel interface(s)",
|
||||
pc_interfaces.len()
|
||||
);
|
||||
for pc in &self.score.port_channels {
|
||||
debug!(
|
||||
" port-channel {} ({}): mode={:?}, vlans={:?}, speed={:?}",
|
||||
pc.id, pc.name, pc.mode, pc.trunk_vlans, pc.speed
|
||||
);
|
||||
}
|
||||
topology
|
||||
.configure_interfaces(&pc_interfaces)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
InterpretError::new(format!(
|
||||
"Failed to configure port-channel interfaces: {e}"
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn configure_standalone_interfaces<T: Topology + Switch>(
|
||||
&self,
|
||||
topology: &T,
|
||||
) -> Result<(), InterpretError> {
|
||||
if !self.score.interfaces.is_empty() {
|
||||
info!(
|
||||
"Configuring {} standalone interface(s)",
|
||||
self.score.interfaces.len()
|
||||
);
|
||||
for iface in &self.score.interfaces {
|
||||
debug!(
|
||||
" {}: mode={:?}, speed={:?}",
|
||||
iface.interface, iface.mode, iface.speed
|
||||
);
|
||||
}
|
||||
topology
|
||||
.configure_interfaces(&self.score.interfaces)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
InterpretError::new(format!("Failed to configure interfaces: {e}"))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,6 @@ pub use brocade::*;
|
||||
|
||||
pub mod brocade_snmp;
|
||||
pub use brocade_snmp::*;
|
||||
|
||||
pub mod brocade_switch_configuration;
|
||||
pub use brocade_switch_configuration::*;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use brocade::{InterfaceConfig, PortChannelConfig, PortChannelId, Vlan};
|
||||
use harmony_types::{id::Id, switch::PortLocation};
|
||||
use log::{error, info, warn};
|
||||
use serde::Serialize;
|
||||
@@ -11,7 +12,10 @@ use crate::{
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::{HostNetworkConfig, NetworkInterface, NetworkManager, Switch, SwitchPort, Topology},
|
||||
topology::{
|
||||
HostNetworkConfig, NetworkInterface, NetworkManager, Switch, SwitchPort,
|
||||
Topology,
|
||||
},
|
||||
};
|
||||
|
||||
/// Configures high-availability networking for a set of physical hosts.
|
||||
@@ -152,8 +156,9 @@ impl HostNetworkConfigurationInterpret {
|
||||
InterpretError::new(format!("Failed to configure host network: {e}"))
|
||||
})?;
|
||||
|
||||
let channel_id = todo!("Determine port-channel ID for this host");
|
||||
topology
|
||||
.configure_port_channel(&config)
|
||||
.configure_port_channel(channel_id, &config)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
InterpretError::new(format!("Failed to configure host network: {e}"))
|
||||
@@ -389,7 +394,7 @@ mod tests {
|
||||
use crate::{
|
||||
hardware::HostCategory,
|
||||
topology::{
|
||||
HostNetworkConfig, NetworkError, PortConfig, PreparationError, PreparationOutcome,
|
||||
HostNetworkConfig, NetworkError, PreparationError, PreparationOutcome,
|
||||
SwitchError, SwitchPort,
|
||||
},
|
||||
};
|
||||
@@ -836,6 +841,7 @@ mod tests {
|
||||
|
||||
async fn configure_port_channel(
|
||||
&self,
|
||||
_channel_id: PortChannelId,
|
||||
config: &HostNetworkConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
let mut configured_port_channels = self.configured_port_channels.lock().unwrap();
|
||||
@@ -843,14 +849,26 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn configure_port_channel_from_config(
|
||||
&self,
|
||||
_config: &PortChannelConfig,
|
||||
) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn clear_port_channel(&self, ids: &Vec<Id>) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn configure_interface(
|
||||
async fn configure_interfaces(
|
||||
&self,
|
||||
port_config: &Vec<PortConfig>,
|
||||
_interfaces: &Vec<InterfaceConfig>,
|
||||
) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn create_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
async fn delete_vlan(&self, _vlan: &Vlan) -> Result<(), SwitchError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user