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 {
|
||||||
"Network bond successfully persisted".into(),
|
Ok(_) => Ok(Outcome::success(
|
||||||
))
|
"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