diff --git a/Cargo.lock b/Cargo.lock index 666fe3a..7d9cdcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1780,6 +1780,7 @@ dependencies = [ name = "example-nanodc" version = "0.1.0" dependencies = [ + "brocade", "cidr", "env_logger", "harmony", @@ -1788,6 +1789,7 @@ dependencies = [ "harmony_tui", "harmony_types", "log", + "serde", "tokio", "url", ] @@ -1806,6 +1808,7 @@ dependencies = [ name = "example-okd-install" version = "0.1.0" dependencies = [ + "brocade", "cidr", "env_logger", "harmony", @@ -1836,13 +1839,16 @@ dependencies = [ name = "example-opnsense" version = "0.1.0" dependencies = [ + "brocade", "cidr", "env_logger", "harmony", "harmony_macros", + "harmony_secret", "harmony_tui", "harmony_types", "log", + "serde", "tokio", "url", ] @@ -1851,6 +1857,7 @@ dependencies = [ name = "example-pxe" version = "0.1.0" dependencies = [ + "brocade", "cidr", "env_logger", "harmony", @@ -1865,6 +1872,15 @@ dependencies = [ "url", ] +[[package]] +name = "example-remove-rook-osd" +version = "0.1.0" +dependencies = [ + "harmony", + "harmony_cli", + "tokio", +] + [[package]] name = "example-rust" version = "0.1.0" @@ -1918,8 +1934,6 @@ dependencies = [ "env_logger", "harmony", "harmony_macros", - "harmony_secret", - "harmony_secret_derive", "harmony_tui", "harmony_types", "log", @@ -4613,15 +4627,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" -[[package]] -name = "remove_rook_osd" -version = "0.1.0" -dependencies = [ - "harmony", - "harmony_cli", - "tokio", -] - [[package]] name = "reqwest" version = "0.11.27" diff --git a/brocade/src/fast_iron.rs b/brocade/src/fast_iron.rs index a1a2478..5a3474e 100644 --- a/brocade/src/fast_iron.rs +++ b/brocade/src/fast_iron.rs @@ -10,6 +10,7 @@ use log::{debug, info}; use regex::Regex; use std::{collections::HashSet, str::FromStr}; +#[derive(Debug)] pub struct FastIronClient { shell: BrocadeShell, version: BrocadeInfo, diff --git a/brocade/src/lib.rs b/brocade/src/lib.rs index 3822abd..57b464a 100644 --- a/brocade/src/lib.rs +++ b/brocade/src/lib.rs @@ -162,7 +162,7 @@ pub async fn init( } #[async_trait] -pub trait BrocadeClient { +pub trait BrocadeClient: std::fmt::Debug { /// Retrieves the operating system and version details from the connected Brocade switch. /// /// This is typically the first call made after establishing a connection to determine diff --git a/brocade/src/network_operating_system.rs b/brocade/src/network_operating_system.rs index b14bc08..0ee4a88 100644 --- a/brocade/src/network_operating_system.rs +++ b/brocade/src/network_operating_system.rs @@ -10,6 +10,7 @@ use crate::{ parse_brocade_mac_address, shell::BrocadeShell, }; +#[derive(Debug)] pub struct NetworkOperatingSystemClient { shell: BrocadeShell, version: BrocadeInfo, diff --git a/brocade/src/shell.rs b/brocade/src/shell.rs index cfa672d..28eceb8 100644 --- a/brocade/src/shell.rs +++ b/brocade/src/shell.rs @@ -13,6 +13,7 @@ use log::info; use russh::ChannelMsg; use tokio::time::timeout; +#[derive(Debug)] pub struct BrocadeShell { ip: IpAddr, port: u16, diff --git a/examples/nanodc/Cargo.toml b/examples/nanodc/Cargo.toml index 889c24d..3072ddf 100644 --- a/examples/nanodc/Cargo.toml +++ b/examples/nanodc/Cargo.toml @@ -17,3 +17,5 @@ harmony_secret = { path = "../../harmony_secret" } log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } +serde = { workspace = true } +brocade = { path = "../../brocade" } diff --git a/examples/nanodc/src/main.rs b/examples/nanodc/src/main.rs index 57574d2..d00503f 100644 --- a/examples/nanodc/src/main.rs +++ b/examples/nanodc/src/main.rs @@ -3,12 +3,13 @@ use std::{ sync::Arc, }; +use brocade::BrocadeOptions; use cidr::Ipv4Cidr; use harmony::{ config::secret::SshKeyPair, data::{FileContent, FilePath}, hardware::{HostCategory, Location, PhysicalHost, SwitchGroup}, - infra::opnsense::OPNSenseManagementInterface, + infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface}, inventory::Inventory, modules::{ http::StaticFilesHttpScore, @@ -22,8 +23,9 @@ use harmony::{ topology::{LogicalHost, UnmanagedRouter}, }; use harmony_macros::{ip, mac_address}; -use harmony_secret::SecretManager; +use harmony_secret::{Secret, SecretManager}; use harmony_types::net::Url; +use serde::{Deserialize, Serialize}; #[tokio::main] async fn main() { @@ -32,6 +34,26 @@ async fn main() { name: String::from("fw0"), }; + let switch_auth = SecretManager::get_or_prompt::() + .await + .expect("Failed to get credentials"); + + let switches: Vec = vec![ip!("192.168.33.101")]; + let brocade_options = Some(BrocadeOptions { + dry_run: *harmony::config::DRY_RUN, + ..Default::default() + }); + let switch_client = BrocadeSwitchClient::init( + &switches, + &switch_auth.username, + &switch_auth.password, + brocade_options, + ) + .await + .expect("Failed to connect to switch"); + + let switch_client = Arc::new(switch_client); + let opnsense = Arc::new( harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await, ); @@ -83,7 +105,7 @@ async fn main() { name: "wk2".to_string(), }, ], - switch: vec![], + switch_client: switch_client.clone(), }; let inventory = Inventory { @@ -166,3 +188,9 @@ async fn main() { .await .unwrap(); } + +#[derive(Secret, Serialize, Deserialize, Debug)] +pub struct BrocadeSwitchAuth { + pub username: String, + pub password: String, +} diff --git a/examples/okd_installation/Cargo.toml b/examples/okd_installation/Cargo.toml index 7314e4f..dfbe944 100644 --- a/examples/okd_installation/Cargo.toml +++ b/examples/okd_installation/Cargo.toml @@ -19,3 +19,4 @@ log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } serde.workspace = true +brocade = { path = "../../brocade" } diff --git a/examples/okd_installation/src/topology.rs b/examples/okd_installation/src/topology.rs index 31062f5..617a3a8 100644 --- a/examples/okd_installation/src/topology.rs +++ b/examples/okd_installation/src/topology.rs @@ -1,7 +1,8 @@ +use brocade::BrocadeOptions; use cidr::Ipv4Cidr; use harmony::{ hardware::{Location, SwitchGroup}, - infra::opnsense::OPNSenseManagementInterface, + infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface}, inventory::Inventory, topology::{HAClusterTopology, LogicalHost, UnmanagedRouter}, }; @@ -22,6 +23,26 @@ pub async fn get_topology() -> HAClusterTopology { name: String::from("opnsense-1"), }; + let switch_auth = SecretManager::get_or_prompt::() + .await + .expect("Failed to get credentials"); + + let switches: Vec = vec![ip!("192.168.1.101")]; // TODO: Adjust me + let brocade_options = Some(BrocadeOptions { + dry_run: *harmony::config::DRY_RUN, + ..Default::default() + }); + let switch_client = BrocadeSwitchClient::init( + &switches, + &switch_auth.username, + &switch_auth.password, + brocade_options, + ) + .await + .expect("Failed to connect to switch"); + + let switch_client = Arc::new(switch_client); + let config = SecretManager::get_or_prompt::().await; let config = config.unwrap(); @@ -58,7 +79,7 @@ pub async fn get_topology() -> HAClusterTopology { name: "bootstrap".to_string(), }, workers: vec![], - switch: vec![], + switch_client: switch_client.clone(), } } @@ -75,3 +96,9 @@ pub fn get_inventory() -> Inventory { control_plane_host: vec![], } } + +#[derive(Secret, Serialize, Deserialize, Debug)] +pub struct BrocadeSwitchAuth { + pub username: String, + pub password: String, +} diff --git a/examples/okd_pxe/Cargo.toml b/examples/okd_pxe/Cargo.toml index f75f42b..133b2f9 100644 --- a/examples/okd_pxe/Cargo.toml +++ b/examples/okd_pxe/Cargo.toml @@ -19,3 +19,4 @@ log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } serde.workspace = true +brocade = { path = "../../brocade" } diff --git a/examples/okd_pxe/src/topology.rs b/examples/okd_pxe/src/topology.rs index 707969a..0cf4b72 100644 --- a/examples/okd_pxe/src/topology.rs +++ b/examples/okd_pxe/src/topology.rs @@ -1,13 +1,15 @@ +use brocade::BrocadeOptions; use cidr::Ipv4Cidr; use harmony::{ config::secret::OPNSenseFirewallCredentials, hardware::{Location, SwitchGroup}, - infra::opnsense::OPNSenseManagementInterface, + infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface}, inventory::Inventory, topology::{HAClusterTopology, LogicalHost, UnmanagedRouter}, }; use harmony_macros::{ip, ipv4}; -use harmony_secret::SecretManager; +use harmony_secret::{Secret, SecretManager}; +use serde::{Deserialize, Serialize}; use std::{net::IpAddr, sync::Arc}; pub async fn get_topology() -> HAClusterTopology { @@ -16,6 +18,26 @@ pub async fn get_topology() -> HAClusterTopology { name: String::from("opnsense-1"), }; + let switch_auth = SecretManager::get_or_prompt::() + .await + .expect("Failed to get credentials"); + + let switches: Vec = vec![ip!("192.168.1.101")]; // TODO: Adjust me + let brocade_options = Some(BrocadeOptions { + dry_run: *harmony::config::DRY_RUN, + ..Default::default() + }); + let switch_client = BrocadeSwitchClient::init( + &switches, + &switch_auth.username, + &switch_auth.password, + brocade_options, + ) + .await + .expect("Failed to connect to switch"); + + let switch_client = Arc::new(switch_client); + let config = SecretManager::get_or_prompt::().await; let config = config.unwrap(); @@ -52,7 +74,7 @@ pub async fn get_topology() -> HAClusterTopology { name: "cp0".to_string(), }, workers: vec![], - switch: vec![], + switch_client: switch_client.clone(), } } @@ -69,3 +91,9 @@ pub fn get_inventory() -> Inventory { control_plane_host: vec![], } } + +#[derive(Secret, Serialize, Deserialize, Debug)] +pub struct BrocadeSwitchAuth { + pub username: String, + pub password: String, +} diff --git a/examples/opnsense/Cargo.toml b/examples/opnsense/Cargo.toml index 60986d3..1574f29 100644 --- a/examples/opnsense/Cargo.toml +++ b/examples/opnsense/Cargo.toml @@ -16,3 +16,6 @@ harmony_macros = { path = "../../harmony_macros" } log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } +harmony_secret = { path = "../../harmony_secret" } +brocade = { path = "../../brocade" } +serde = { workspace = true } diff --git a/examples/opnsense/src/main.rs b/examples/opnsense/src/main.rs index fcfaf09..d03643b 100644 --- a/examples/opnsense/src/main.rs +++ b/examples/opnsense/src/main.rs @@ -3,10 +3,11 @@ use std::{ sync::Arc, }; +use brocade::BrocadeOptions; use cidr::Ipv4Cidr; use harmony::{ hardware::{HostCategory, Location, PhysicalHost, SwitchGroup}, - infra::opnsense::OPNSenseManagementInterface, + infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface}, inventory::Inventory, modules::{ dummy::{ErrorScore, PanicScore, SuccessScore}, @@ -18,7 +19,9 @@ use harmony::{ topology::{LogicalHost, UnmanagedRouter}, }; use harmony_macros::{ip, mac_address}; +use harmony_secret::{Secret, SecretManager}; use harmony_types::net::Url; +use serde::{Deserialize, Serialize}; #[tokio::main] async fn main() { @@ -27,6 +30,26 @@ async fn main() { name: String::from("opnsense-1"), }; + let switch_auth = SecretManager::get_or_prompt::() + .await + .expect("Failed to get credentials"); + + let switches: Vec = vec![ip!("192.168.5.101")]; // TODO: Adjust me + let brocade_options = Some(BrocadeOptions { + dry_run: *harmony::config::DRY_RUN, + ..Default::default() + }); + let switch_client = BrocadeSwitchClient::init( + &switches, + &switch_auth.username, + &switch_auth.password, + brocade_options, + ) + .await + .expect("Failed to connect to switch"); + + let switch_client = Arc::new(switch_client); + let opnsense = Arc::new( harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await, ); @@ -54,7 +77,7 @@ async fn main() { name: "cp0".to_string(), }, workers: vec![], - switch: vec![], + switch_client: switch_client.clone(), }; let inventory = Inventory { @@ -109,3 +132,9 @@ async fn main() { .await .unwrap(); } + +#[derive(Secret, Serialize, Deserialize, Debug)] +pub struct BrocadeSwitchAuth { + pub username: String, + pub password: String, +} diff --git a/harmony/src/domain/topology/ha_cluster.rs b/harmony/src/domain/topology/ha_cluster.rs index 7be2725..59787a1 100644 --- a/harmony/src/domain/topology/ha_cluster.rs +++ b/harmony/src/domain/topology/ha_cluster.rs @@ -1,7 +1,5 @@ use async_trait::async_trait; -use brocade::BrocadeOptions; use harmony_macros::ip; -use harmony_secret::SecretManager; use harmony_types::{ net::{MacAddress, Url}, switch::PortLocation, @@ -14,8 +12,6 @@ use log::info; use crate::data::FileContent; use crate::executors::ExecutorError; use crate::hardware::PhysicalHost; -use crate::infra::brocade::BrocadeSwitchAuth; -use crate::infra::brocade::BrocadeSwitchClient; use crate::modules::okd::crd::{ InstallPlanApproval, OperatorGroup, OperatorGroupSpec, Subscription, SubscriptionSpec, nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec}, @@ -30,7 +26,6 @@ use super::{ }; use std::collections::BTreeMap; -use std::net::IpAddr; use std::sync::Arc; #[derive(Debug, Clone)] @@ -43,10 +38,10 @@ pub struct HAClusterTopology { pub tftp_server: Arc, pub http_server: Arc, pub dns_server: Arc, + pub switch_client: Arc, pub bootstrap_host: LogicalHost, pub control_plane: Vec, pub workers: Vec, - pub switch: Vec, } #[async_trait] @@ -280,36 +275,15 @@ impl HAClusterTopology { } } - async fn get_switch_client(&self) -> Result, SwitchError> { - let auth = SecretManager::get_or_prompt::() - .await - .map_err(|e| SwitchError::new(format!("Failed to get credentials: {e}")))?; - - // FIXME: We assume Brocade switches - let switches: Vec = self.switch.iter().map(|s| s.ip).collect(); - let brocade_options = Some(BrocadeOptions { - dry_run: *crate::config::DRY_RUN, - ..Default::default() - }); - let client = - BrocadeSwitchClient::init(&switches, &auth.username, &auth.password, brocade_options) - .await - .map_err(|e| SwitchError::new(format!("Failed to connect to switch: {e}")))?; - - Ok(Box::new(client)) - } - async fn configure_port_channel( &self, host: &PhysicalHost, config: &HostNetworkConfig, ) -> Result<(), SwitchError> { debug!("Configuring port channel: {config:#?}"); - let client = self.get_switch_client().await?; - let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect(); - client + self.switch_client .configure_port_channel(&format!("Harmony_{}", host.id), switch_ports) .await .map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?; @@ -333,10 +307,10 @@ impl HAClusterTopology { tftp_server: dummy_infra.clone(), http_server: dummy_infra.clone(), dns_server: dummy_infra.clone(), + switch_client: dummy_infra.clone(), bootstrap_host: dummy_host, control_plane: vec![], workers: vec![], - switch: vec![], } } } @@ -494,8 +468,7 @@ impl HttpServer for HAClusterTopology { #[async_trait] impl Switch for HAClusterTopology { async fn setup_switch(&self) -> Result<(), SwitchError> { - let client = self.get_switch_client().await?; - client.setup().await?; + self.switch_client.setup().await?; Ok(()) } @@ -503,8 +476,7 @@ impl Switch for HAClusterTopology { &self, mac_address: &MacAddress, ) -> Result, SwitchError> { - let client = self.get_switch_client().await?; - let port = client.find_port(mac_address).await?; + let port = self.switch_client.find_port(mac_address).await?; Ok(port) } @@ -704,3 +676,25 @@ impl DnsServer for DummyInfra { unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) } } + +#[async_trait] +impl SwitchClient for DummyInfra { + async fn setup(&self) -> Result<(), SwitchError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } + + async fn find_port( + &self, + _mac_address: &MacAddress, + ) -> Result, SwitchError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } + + async fn configure_port_channel( + &self, + _channel_name: &str, + _switch_ports: Vec, + ) -> Result { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } +} diff --git a/harmony/src/domain/topology/network.rs b/harmony/src/domain/topology/network.rs index 99db03a..b78f1a0 100644 --- a/harmony/src/domain/topology/network.rs +++ b/harmony/src/domain/topology/network.rs @@ -1,4 +1,10 @@ -use std::{error::Error, net::Ipv4Addr, str::FromStr, sync::Arc}; +use std::{ + error::Error, + fmt::{self, Debug}, + net::Ipv4Addr, + str::FromStr, + sync::Arc, +}; use async_trait::async_trait; use derive_new::new; @@ -19,8 +25,8 @@ pub struct DHCPStaticEntry { pub ip: Ipv4Addr, } -impl std::fmt::Display for DHCPStaticEntry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for DHCPStaticEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mac = self .mac .iter() @@ -42,8 +48,8 @@ pub trait Firewall: Send + Sync { fn get_host(&self) -> LogicalHost; } -impl std::fmt::Debug for dyn Firewall { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Debug for dyn Firewall { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("Firewall {}", self.get_ip())) } } @@ -65,7 +71,7 @@ pub struct PxeOptions { } #[async_trait] -pub trait DhcpServer: Send + Sync + std::fmt::Debug { +pub trait DhcpServer: Send + Sync + Debug { async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>; async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>; async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; @@ -104,8 +110,8 @@ pub trait DnsServer: Send + Sync { } } -impl std::fmt::Debug for dyn DnsServer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Debug for dyn DnsServer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("DnsServer {}", self.get_ip())) } } @@ -141,8 +147,8 @@ pub enum DnsRecordType { TXT, } -impl std::fmt::Display for DnsRecordType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for DnsRecordType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DnsRecordType::A => write!(f, "A"), DnsRecordType::AAAA => write!(f, "AAAA"), @@ -216,8 +222,8 @@ pub struct SwitchError { msg: String, } -impl std::fmt::Display for SwitchError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SwitchError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.msg) } } @@ -225,7 +231,7 @@ impl std::fmt::Display for SwitchError { impl Error for SwitchError {} #[async_trait] -pub trait SwitchClient: Send + Sync { +pub trait SwitchClient: Debug + Send + Sync { /// Executes essential, idempotent, one-time initial configuration steps. /// /// This is an opiniated procedure that setups a switch to provide high availability diff --git a/harmony/src/infra/brocade.rs b/harmony/src/infra/brocade.rs index f721328..774c8f8 100644 --- a/harmony/src/infra/brocade.rs +++ b/harmony/src/infra/brocade.rs @@ -1,15 +1,14 @@ use async_trait::async_trait; use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode}; -use harmony_secret::Secret; use harmony_types::{ net::{IpAddress, MacAddress}, switch::{PortDeclaration, PortLocation}, }; use option_ext::OptionExt; -use serde::{Deserialize, Serialize}; use crate::topology::{SwitchClient, SwitchError}; +#[derive(Debug)] pub struct BrocadeSwitchClient { brocade: Box, } @@ -114,12 +113,6 @@ impl SwitchClient for BrocadeSwitchClient { } } -#[derive(Secret, Serialize, Deserialize, Debug)] -pub struct BrocadeSwitchAuth { - pub username: String, - pub password: String, -} - #[cfg(test)] mod tests { use std::sync::{Arc, Mutex}; @@ -235,7 +228,7 @@ mod tests { assert_that!(*configured_interfaces).is_empty(); } - #[derive(Clone)] + #[derive(Debug, Clone)] struct FakeBrocadeClient { stack_topology: Vec, interfaces: Vec,