fix(host_network): adjust bond & port-channel configuration (partial) #175
| @ -31,6 +31,7 @@ pub struct BrocadeOptions { | |||||||
| pub struct TimeoutConfig { | pub struct TimeoutConfig { | ||||||
|     pub shell_ready: Duration, |     pub shell_ready: Duration, | ||||||
|     pub command_execution: Duration, |     pub command_execution: Duration, | ||||||
|  |     pub command_output: Duration, | ||||||
|     pub cleanup: Duration, |     pub cleanup: Duration, | ||||||
|     pub message_wait: Duration, |     pub message_wait: Duration, | ||||||
| } | } | ||||||
| @ -40,6 +41,7 @@ impl Default for TimeoutConfig { | |||||||
|         Self { |         Self { | ||||||
|             shell_ready: Duration::from_secs(10), |             shell_ready: Duration::from_secs(10), | ||||||
|             command_execution: Duration::from_secs(60), // Commands like `deploy` (for a LAG) can take a while
 |             command_execution: Duration::from_secs(60), // Commands like `deploy` (for a LAG) can take a while
 | ||||||
|  |             command_output: Duration::from_secs(5), // Delay to start logging "waiting for command output"
 | ||||||
|             cleanup: Duration::from_secs(10), |             cleanup: Duration::from_secs(10), | ||||||
|             message_wait: Duration::from_millis(500), |             message_wait: Duration::from_millis(500), | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ use std::str::FromStr; | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use harmony_types::switch::{PortDeclaration, PortLocation}; | use harmony_types::switch::{PortDeclaration, PortLocation}; | ||||||
| use log::{debug, info}; | use log::{debug, info}; | ||||||
|  | use regex::Regex; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, |     BrocadeClient, BrocadeInfo, Error, ExecutionMode, InterSwitchLink, InterfaceInfo, | ||||||
| @ -110,6 +111,30 @@ impl NetworkOperatingSystemClient { | |||||||
|             status, |             status, | ||||||
|         })) |         })) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn map_configure_interfaces_error(&self, err: Error) -> Error { | ||||||
|  |         debug!("[Brocade] {err}"); | ||||||
|  | 
 | ||||||
|  |         if let Error::CommandError(message) = &err { | ||||||
|  |             if message.contains("switchport") | ||||||
|  |                 && message.contains("Cannot configure aggregator member") | ||||||
|  |             { | ||||||
|  |                 let re = Regex::new(r"\(conf-if-([a-zA-Z]+)-([\d/]+)\)#").unwrap(); | ||||||
|  | 
 | ||||||
|  |                 if let Some(caps) = re.captures(message) { | ||||||
|  |                     let interface_type = &caps[1]; | ||||||
|  |                     let port_location = &caps[2]; | ||||||
|  |                     let interface = format!("{interface_type} {port_location}"); | ||||||
|  | 
 | ||||||
|  |                     return Error::CommandError(format!( | ||||||
|  |                         "Cannot configure interface '{interface}', it is a member of a port-channel (LAG)" | ||||||
|  |                     )); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         err | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| @ -199,7 +224,8 @@ impl BrocadeClient for NetworkOperatingSystemClient { | |||||||
| 
 | 
 | ||||||
|         self.shell |         self.shell | ||||||
|             .run_commands(commands, ExecutionMode::Regular) |             .run_commands(commands, ExecutionMode::Regular) | ||||||
|             .await?; |             .await | ||||||
|  |             .map_err(|err| self.map_configure_interfaces_error(err))?; | ||||||
| 
 | 
 | ||||||
|         info!("[Brocade] Interfaces configured."); |         info!("[Brocade] Interfaces configured."); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -211,7 +211,7 @@ impl BrocadeSession { | |||||||
|         let mut output = Vec::new(); |         let mut output = Vec::new(); | ||||||
|         let start = Instant::now(); |         let start = Instant::now(); | ||||||
|         let read_timeout = Duration::from_millis(500); |         let read_timeout = Duration::from_millis(500); | ||||||
|         let log_interval = Duration::from_secs(3); |         let log_interval = Duration::from_secs(5); | ||||||
|         let mut last_log = Instant::now(); |         let mut last_log = Instant::now(); | ||||||
| 
 | 
 | ||||||
|         loop { |         loop { | ||||||
| @ -221,7 +221,9 @@ impl BrocadeSession { | |||||||
|                 )); |                 )); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if start.elapsed() > Duration::from_secs(5) && last_log.elapsed() > log_interval { |             if start.elapsed() > self.options.timeouts.command_output | ||||||
|  |                 && last_log.elapsed() > log_interval | ||||||
|  |             { | ||||||
|                 info!("[Brocade] Waiting for command output..."); |                 info!("[Brocade] Waiting for command output..."); | ||||||
|                 last_log = Instant::now(); |                 last_log = Instant::now(); | ||||||
|             } |             } | ||||||
| @ -276,7 +278,7 @@ impl BrocadeSession { | |||||||
|         let output_lower = output.to_lowercase(); |         let output_lower = output.to_lowercase(); | ||||||
|         if ERROR_PATTERNS.iter().any(|&p| output_lower.contains(p)) { |         if ERROR_PATTERNS.iter().any(|&p| output_lower.contains(p)) { | ||||||
|             return Err(Error::CommandError(format!( |             return Err(Error::CommandError(format!( | ||||||
|                 "Command '{command}' failed: {}", |                 "Command error: {}", | ||||||
|                 output.trim() |                 output.trim() | ||||||
|             ))); |             ))); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -167,7 +167,7 @@ impl HAClusterTopology { | |||||||
| 
 | 
 | ||||||
|         let bond_config = self.create_bond_configuration(config); |         let bond_config = self.create_bond_configuration(config); | ||||||
|         debug!( |         debug!( | ||||||
|             "Configuring bond for host {}: {bond_config:#?}", |             "Applying NMState bond config for host {}: {bond_config:#?}", | ||||||
|             config.host_id |             config.host_id | ||||||
|         ); |         ); | ||||||
|         self.k8s_client() |         self.k8s_client() | ||||||
| @ -185,9 +185,11 @@ impl HAClusterTopology { | |||||||
|         config: &HostNetworkConfig, |         config: &HostNetworkConfig, | ||||||
|     ) -> NodeNetworkConfigurationPolicy { |     ) -> NodeNetworkConfigurationPolicy { | ||||||
|         let host_name = &config.host_id; |         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}"); | ||||||
|  | 
 | ||||||
|  |         info!("Configuring bond '{bond_name}' for host '{host_name}'..."); | ||||||
|  | 
 | ||||||
|         let mut bond_mtu: Option<u32> = None; |         let mut bond_mtu: Option<u32> = None; | ||||||
|         let mut bond_mac_address: Option<String> = None; |         let mut bond_mac_address: Option<String> = None; | ||||||
|         let mut bond_ports = Vec::new(); |         let mut bond_ports = Vec::new(); | ||||||
|  | |||||||
| @ -116,11 +116,15 @@ impl Interpret<HAClusterTopology> for OKDSetupPersistNetworkBondInterpet { | |||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         let nodes = self.get_nodes(inventory, topology).await?; |         let nodes = self.get_nodes(inventory, topology).await?; | ||||||
| 
 | 
 | ||||||
|         self.persist_network_bond(inventory, topology, &nodes) |         let res = self.persist_network_bond(inventory, topology, &nodes).await; | ||||||
|             .await?; |  | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success( |         match res { | ||||||
|  |             Ok(_) => Ok(Outcome::success( | ||||||
|                 "Network bond successfully persisted".into(), |                 "Network bond successfully persisted".into(), | ||||||
|         )) |             )), | ||||||
|  |             Err(_) => Err(InterpretError::new( | ||||||
|  |                 "Failed to persist network bond".to_string(), | ||||||
|  |             )), | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -42,8 +42,17 @@ impl HostNetworkConfigurationInterpret { | |||||||
|         current_host: &usize, |         current_host: &usize, | ||||||
|         total_hosts: &usize, |         total_hosts: &usize, | ||||||
|     ) -> Result<HostNetworkConfig, InterpretError> { |     ) -> Result<HostNetworkConfig, InterpretError> { | ||||||
|         info!("[Host {current_host}/{total_hosts}] Collecting ports on switch..."); |         if host.network.is_empty() { | ||||||
|         let switch_ports = self.collect_switch_ports_for_host(topology, host).await?; |             info!("[Host {current_host}/{total_hosts}] No interfaces to configure, skipping"); | ||||||
|  |             return Ok(HostNetworkConfig { | ||||||
|  |                 host_id: host.id.clone(), | ||||||
|  |                 switch_ports: vec![], | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let switch_ports = self | ||||||
|  |             .collect_switch_ports_for_host(topology, host, current_host, total_hosts) | ||||||
|  |             .await?; | ||||||
| 
 | 
 | ||||||
|         let config = HostNetworkConfig { |         let config = HostNetworkConfig { | ||||||
|             host_id: host.id.clone(), |             host_id: host.id.clone(), | ||||||
| @ -63,7 +72,10 @@ impl HostNetworkConfigurationInterpret { | |||||||
|                 .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 { |         } else { | ||||||
|             info!("[Host {current_host}/{total_hosts}] No ports found"); |             info!( | ||||||
|  |                 "[Host {current_host}/{total_hosts}] No ports found for {} interfaces, skipping", | ||||||
|  |                 host.network.len() | ||||||
|  |             ); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Ok(config) |         Ok(config) | ||||||
| @ -73,14 +85,24 @@ impl HostNetworkConfigurationInterpret { | |||||||
|         &self, |         &self, | ||||||
|         topology: &T, |         topology: &T, | ||||||
|         host: &PhysicalHost, |         host: &PhysicalHost, | ||||||
|  |         current_host: &usize, | ||||||
|  |         total_hosts: &usize, | ||||||
|     ) -> Result<Vec<SwitchPort>, InterpretError> { |     ) -> Result<Vec<SwitchPort>, InterpretError> { | ||||||
|         let mut switch_ports = vec![]; |         let mut switch_ports = vec![]; | ||||||
| 
 | 
 | ||||||
|  |         if host.network.is_empty() { | ||||||
|  |             return Ok(switch_ports); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         info!("[Host {current_host}/{total_hosts}] Collecting ports on switch..."); | ||||||
|         for network_interface in &host.network { |         for network_interface in &host.network { | ||||||
|             let mac_address = network_interface.mac_address; |             let mac_address = network_interface.mac_address; | ||||||
| 
 | 
 | ||||||
|             match topology.get_port_for_mac_address(&mac_address).await { |             match topology.get_port_for_mac_address(&mac_address).await { | ||||||
|                 Ok(Some(port)) => { |                 Ok(Some(port)) => { | ||||||
|  |                     info!( | ||||||
|  |                         "[Host {current_host}/{total_hosts}] Found port '{port}' for '{mac_address}'" | ||||||
|  |                     ); | ||||||
|                     switch_ports.push(SwitchPort { |                     switch_ports.push(SwitchPort { | ||||||
|                         interface: NetworkInterface { |                         interface: NetworkInterface { | ||||||
|                             name: network_interface.name.clone(), |                             name: network_interface.name.clone(), | ||||||
| @ -91,7 +113,7 @@ impl HostNetworkConfigurationInterpret { | |||||||
|                         port, |                         port, | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|                 Ok(None) => debug!("No port found for host '{}', skipping", host.id), |                 Ok(None) => debug!("No port found for '{mac_address}', skipping"), | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     return Err(InterpretError::new(format!( |                     return Err(InterpretError::new(format!( | ||||||
|                         "Failed to get port for host '{}': {}", |                         "Failed to get port for host '{}': {}", | ||||||
| @ -176,10 +198,12 @@ impl<T: Topology + Switch> Interpret<T> for HostNetworkConfigurationInterpret { | |||||||
|         let host_count = self.score.hosts.len(); |         let host_count = self.score.hosts.len(); | ||||||
|         info!("Started network configuration for {host_count} host(s)...",); |         info!("Started network configuration for {host_count} host(s)...",); | ||||||
| 
 | 
 | ||||||
|  |         info!("Setting up switch with sane defaults..."); | ||||||
|         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}")))?; | ||||||
|  |         info!("Switch ready"); | ||||||
| 
 | 
 | ||||||
|         let mut current_host = 1; |         let mut current_host = 1; | ||||||
|         let mut host_configurations = vec![]; |         let mut host_configurations = vec![]; | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ pub fn init() { | |||||||
|                 HarmonyEvent::HarmonyFinished => { |                 HarmonyEvent::HarmonyFinished => { | ||||||
|                     if !details.is_empty() { |                     if !details.is_empty() { | ||||||
|                         println!( |                         println!( | ||||||
|                             "\n{} All done! Here's what's next for you:", |                             "\n{} All done! Here's a few info for you:", | ||||||
|                             theme::EMOJI_SUMMARY |                             theme::EMOJI_SUMMARY | ||||||
|                         ); |                         ); | ||||||
|                         for detail in details.iter() { |                         for detail in details.iter() { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user