add more logging and add reporting message
Some checks failed
Run Check Script / check (pull_request) Failing after 1m23s

This commit is contained in:
Ian Letourneau 2025-10-23 16:37:43 -04:00
parent 2fe1c5d147
commit 89cb23f4f7
5 changed files with 116 additions and 58 deletions

View File

@ -247,7 +247,12 @@ impl BrocadeClient for NetworkOperatingSystemClient {
ports: &[PortLocation], ports: &[PortLocation],
) -> Result<(), Error> { ) -> Result<(), Error> {
info!( info!(
"[Brocade] Configuring port-channel '{channel_name} {channel_id}' with ports: {ports:?}" "[Brocade] Configuring port-channel '{channel_id} {channel_name}' with ports: {}",
ports
.iter()
.map(|p| format!("{p}"))
.collect::<Vec<String>>()
.join(", ")
); );
let interfaces = self.get_interfaces().await?; let interfaces = self.get_interfaces().await?;

View File

@ -12,7 +12,6 @@ use log::info;
use crate::data::FileContent; use crate::data::FileContent;
use crate::executors::ExecutorError; use crate::executors::ExecutorError;
use crate::hardware::PhysicalHost;
use crate::infra::brocade::BrocadeSwitchAuth; use crate::infra::brocade::BrocadeSwitchAuth;
use crate::infra::brocade::BrocadeSwitchClient; use crate::infra::brocade::BrocadeSwitchClient;
use crate::modules::okd::crd::nmstate::{ use crate::modules::okd::crd::nmstate::{
@ -162,11 +161,7 @@ impl HAClusterTopology {
42 // FIXME: Find a better way to declare the bond id 42 // FIXME: Find a better way to declare the bond id
} }
async fn configure_bond( async fn configure_bond(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
&self,
host: &PhysicalHost,
config: &HostNetworkConfig,
) -> Result<(), SwitchError> {
self.ensure_nmstate_operator_installed() self.ensure_nmstate_operator_installed()
.await .await
.map_err(|e| { .map_err(|e| {
@ -175,8 +170,11 @@ impl HAClusterTopology {
)) ))
})?; })?;
let bond_config = self.create_bond_configuration(host, config); let bond_config = self.create_bond_configuration(config);
debug!("Configuring bond for host {host:?}: {bond_config:#?}"); debug!(
"Configuring bond for host {}: {bond_config:#?}",
config.host_id
);
self.k8s_client() self.k8s_client()
.await .await
.unwrap() .unwrap()
@ -189,10 +187,9 @@ impl HAClusterTopology {
fn create_bond_configuration( fn create_bond_configuration(
&self, &self,
host: &PhysicalHost,
config: &HostNetworkConfig, config: &HostNetworkConfig,
) -> NodeNetworkConfigurationPolicy { ) -> NodeNetworkConfigurationPolicy {
let host_name = host.id.clone(); let host_name = &config.host_id;
let bond_id = self.get_next_bond_id(); let bond_id = self.get_next_bond_id();
let bond_name = format!("bond{bond_id}"); let bond_name = format!("bond{bond_id}");
@ -294,18 +291,14 @@ impl HAClusterTopology {
Ok(Box::new(client)) Ok(Box::new(client))
} }
async fn configure_port_channel( async fn configure_port_channel(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
&self,
host: &PhysicalHost,
config: &HostNetworkConfig,
) -> Result<(), SwitchError> {
debug!("Configuring port channel: {config:#?}"); debug!("Configuring port channel: {config:#?}");
let client = self.get_switch_client().await?; let client = self.get_switch_client().await?;
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect(); let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
client client
.configure_port_channel(&format!("Harmony_{}", host.id), switch_ports) .configure_port_channel(&format!("Harmony_{}", config.host_id), switch_ports)
.await .await
.map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?; .map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?;
@ -504,13 +497,9 @@ impl Switch for HAClusterTopology {
Ok(port) Ok(port)
} }
async fn configure_host_network( async fn configure_host_network(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
&self, self.configure_bond(config).await?;
host: &PhysicalHost, self.configure_port_channel(config).await
config: HostNetworkConfig,
) -> Result<(), SwitchError> {
self.configure_bond(host, &config).await?;
self.configure_port_channel(host, &config).await
} }
} }

View File

@ -3,6 +3,7 @@ use std::{error::Error, net::Ipv4Addr, str::FromStr, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use harmony_types::{ use harmony_types::{
id::Id,
net::{IpAddress, MacAddress}, net::{IpAddress, MacAddress},
switch::PortLocation, switch::PortLocation,
}; };
@ -185,15 +186,12 @@ pub trait Switch: Send + Sync {
mac_address: &MacAddress, mac_address: &MacAddress,
) -> Result<Option<PortLocation>, SwitchError>; ) -> Result<Option<PortLocation>, SwitchError>;
async fn configure_host_network( async fn configure_host_network(&self, config: &HostNetworkConfig) -> Result<(), SwitchError>;
&self,
host: &PhysicalHost,
config: HostNetworkConfig,
) -> Result<(), SwitchError>;
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct HostNetworkConfig { pub struct HostNetworkConfig {
pub host_id: Id,
pub switch_ports: Vec<SwitchPort>, pub switch_ports: Vec<SwitchPort>,
} }

View File

@ -15,7 +15,7 @@ use log::info;
use serde::Serialize; use serde::Serialize;
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
// Step XX: Persist Network Bond // Persist Network Bond
// - Persist bonding via NMState // - Persist bonding via NMState
// - Persist port channels on the Switch // - Persist port channels on the Switch
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
@ -80,18 +80,13 @@ impl OKDSetupPersistNetworkBondInterpet {
topology: &HAClusterTopology, topology: &HAClusterTopology,
hosts: &Vec<PhysicalHost>, hosts: &Vec<PhysicalHost>,
) -> Result<(), InterpretError> { ) -> Result<(), InterpretError> {
info!("[ControlPlane] Ensuring persistent bonding"); info!("Ensuring persistent bonding");
let score = HostNetworkConfigurationScore { let score = HostNetworkConfigurationScore {
hosts: hosts.clone(), hosts: hosts.clone(),
}; };
score.interpret(inventory, topology).await?; score.interpret(inventory, topology).await?;
inquire::Confirm::new(
"Network configuration for control plane nodes is not automated yet. Configure it manually if needed.",
)
.prompt()
.map_err(|e| InterpretError::new(format!("User prompt failed: {e}")))?;
Ok(()) Ok(())
} }
} }

View File

@ -39,16 +39,34 @@ impl HostNetworkConfigurationInterpret {
&self, &self,
topology: &T, topology: &T,
host: &PhysicalHost, host: &PhysicalHost,
) -> Result<(), InterpretError> { current_host: &usize,
total_hosts: &usize,
) -> Result<HostNetworkConfig, InterpretError> {
info!("[Host {current_host}/{total_hosts}] Collecting ports on switch...");
let switch_ports = self.collect_switch_ports_for_host(topology, host).await?; let switch_ports = self.collect_switch_ports_for_host(topology, host).await?;
if !switch_ports.is_empty() {
let config = HostNetworkConfig {
host_id: host.id.clone(),
switch_ports,
};
if !config.switch_ports.is_empty() {
info!(
"[Host {current_host}/{total_hosts}] Found {} ports for {} interfaces",
config.switch_ports.len(),
host.network.len()
);
info!("[Host {current_host}/{total_hosts}] Configuring host network...");
topology topology
.configure_host_network(host, HostNetworkConfig { switch_ports }) .configure_host_network(&config)
.await .await
.map_err(|e| InterpretError::new(format!("Failed to configure host: {e}")))?; .map_err(|e| InterpretError::new(format!("Failed to configure host: {e}")))?;
} else {
info!("[Host {current_host}/{total_hosts}] No ports found");
} }
Ok(()) Ok(config)
} }
async fn collect_switch_ports_for_host<T: Topology + Switch>( async fn collect_switch_ports_for_host<T: Topology + Switch>(
@ -85,6 +103,47 @@ impl HostNetworkConfigurationInterpret {
Ok(switch_ports) Ok(switch_ports)
} }
fn format_host_configuration(&self, configs: Vec<HostNetworkConfig>) -> Vec<String> {
let mut report = vec![
"Network Configuration Report".to_string(),
"------------------------------------------------------------------".to_string(),
];
for config in configs {
let host = self
.score
.hosts
.iter()
.find(|h| h.id == config.host_id)
.unwrap();
println!("[Host] {host}");
if config.switch_ports.is_empty() {
report.push(format!(
"⏭️ Host {}: SKIPPED (No matching switch ports found)",
config.host_id
));
} else {
let mappings: Vec<String> = config
.switch_ports
.iter()
.map(|p| format!("[{} -> {}]", p.interface.name, p.port))
.collect();
report.push(format!(
"✅ Host {}: Bonded {} port(s) {}",
config.host_id,
config.switch_ports.len(),
mappings.join(", ")
));
}
}
report
.push("------------------------------------------------------------------".to_string());
report
}
} }
#[async_trait] #[async_trait]
@ -114,27 +173,36 @@ impl<T: Topology + Switch> Interpret<T> for HostNetworkConfigurationInterpret {
return Ok(Outcome::noop("No hosts to configure".into())); return Ok(Outcome::noop("No hosts to configure".into()));
} }
info!( let host_count = self.score.hosts.len();
"Started network configuration for {} host(s)...", info!("Started network configuration for {host_count} host(s)...",);
self.score.hosts.len()
);
topology topology
.setup_switch() .setup_switch()
.await .await
.map_err(|e| InterpretError::new(format!("Switch setup failed: {e}")))?; .map_err(|e| InterpretError::new(format!("Switch setup failed: {e}")))?;
let mut configured_host_count = 0; let mut current_host = 1;
for host in &self.score.hosts { let mut host_configurations = vec![];
self.configure_network_for_host(topology, host).await?;
configured_host_count += 1;
}
if configured_host_count > 0 { for host in &self.score.hosts {
Ok(Outcome::success(format!( let host_configuration = self
"Configured {configured_host_count}/{} host(s)", .configure_network_for_host(topology, host, &current_host, &host_count)
self.score.hosts.len() .await?;
)))
host_configurations.push(host_configuration);
current_host += 1;
}
if current_host > 1 {
let details = self.format_host_configuration(host_configurations);
Ok(Outcome::success_with_details(
format!(
"Configured {}/{} host(s)",
current_host - 1,
self.score.hosts.len()
),
details,
))
} else { } else {
Ok(Outcome::noop("No hosts configured".into())) Ok(Outcome::noop("No hosts configured".into()))
} }
@ -209,6 +277,7 @@ mod tests {
assert_that!(*configured_host_networks).contains_exactly(vec![( assert_that!(*configured_host_networks).contains_exactly(vec![(
HOST_ID.clone(), HOST_ID.clone(),
HostNetworkConfig { HostNetworkConfig {
host_id: HOST_ID.clone(),
switch_ports: vec![SwitchPort { switch_ports: vec![SwitchPort {
interface: EXISTING_INTERFACE.clone(), interface: EXISTING_INTERFACE.clone(),
port: PORT.clone(), port: PORT.clone(),
@ -234,6 +303,7 @@ mod tests {
assert_that!(*configured_host_networks).contains_exactly(vec![( assert_that!(*configured_host_networks).contains_exactly(vec![(
HOST_ID.clone(), HOST_ID.clone(),
HostNetworkConfig { HostNetworkConfig {
host_id: HOST_ID.clone(),
switch_ports: vec![ switch_ports: vec![
SwitchPort { SwitchPort {
interface: EXISTING_INTERFACE.clone(), interface: EXISTING_INTERFACE.clone(),
@ -263,6 +333,7 @@ mod tests {
( (
HOST_ID.clone(), HOST_ID.clone(),
HostNetworkConfig { HostNetworkConfig {
host_id: HOST_ID.clone(),
switch_ports: vec![SwitchPort { switch_ports: vec![SwitchPort {
interface: EXISTING_INTERFACE.clone(), interface: EXISTING_INTERFACE.clone(),
port: PORT.clone(), port: PORT.clone(),
@ -272,6 +343,7 @@ mod tests {
( (
ANOTHER_HOST_ID.clone(), ANOTHER_HOST_ID.clone(),
HostNetworkConfig { HostNetworkConfig {
host_id: ANOTHER_HOST_ID.clone(),
switch_ports: vec![SwitchPort { switch_ports: vec![SwitchPort {
interface: ANOTHER_EXISTING_INTERFACE.clone(), interface: ANOTHER_EXISTING_INTERFACE.clone(),
port: ANOTHER_PORT.clone(), port: ANOTHER_PORT.clone(),
@ -382,11 +454,10 @@ mod tests {
async fn configure_host_network( async fn configure_host_network(
&self, &self,
host: &PhysicalHost, config: &HostNetworkConfig,
config: HostNetworkConfig,
) -> Result<(), SwitchError> { ) -> Result<(), SwitchError> {
let mut configured_host_networks = self.configured_host_networks.lock().unwrap(); let mut configured_host_networks = self.configured_host_networks.lock().unwrap();
configured_host_networks.push((host.id.clone(), config.clone())); configured_host_networks.push((config.host_id.clone(), config.clone()));
Ok(()) Ok(())
} }