feat(switch): configure host network and switch network
This commit is contained in:
parent
c84b2413ed
commit
61b02e7a28
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -429,6 +429,15 @@ dependencies = [
|
|||||||
"wait-timeout",
|
"wait-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assertor"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ff24d87260733dc86d38a11c60d9400ce4a74a05d0dafa2a6f5ab249cd857cb"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@ -2305,6 +2314,7 @@ name = "harmony"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
|
"assertor",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bollard",
|
"bollard",
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -14,7 +14,8 @@ members = [
|
|||||||
"harmony_composer",
|
"harmony_composer",
|
||||||
"harmony_inventory_agent",
|
"harmony_inventory_agent",
|
||||||
"harmony_secret_derive",
|
"harmony_secret_derive",
|
||||||
"harmony_secret", "adr/agent_discovery/mdns",
|
"harmony_secret",
|
||||||
|
"adr/agent_discovery/mdns",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@ -67,4 +68,11 @@ serde = { version = "1.0.209", features = ["derive", "rc"] }
|
|||||||
serde_json = "1.0.127"
|
serde_json = "1.0.127"
|
||||||
askama = "0.14"
|
askama = "0.14"
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||||
reqwest = { version = "0.12", features = ["blocking", "stream", "rustls-tls", "http2", "json"], default-features = false }
|
reqwest = { version = "0.12", features = [
|
||||||
|
"blocking",
|
||||||
|
"stream",
|
||||||
|
"rustls-tls",
|
||||||
|
"http2",
|
||||||
|
"json",
|
||||||
|
], default-features = false }
|
||||||
|
assertor = "0.0.4"
|
||||||
|
@ -80,3 +80,4 @@ inquire.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
assertor.workspace = true
|
||||||
|
@ -7,6 +7,7 @@ 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::topology::PxeOptions;
|
use crate::topology::PxeOptions;
|
||||||
|
|
||||||
use super::DHCPStaticEntry;
|
use super::DHCPStaticEntry;
|
||||||
@ -15,6 +16,7 @@ use super::DnsRecord;
|
|||||||
use super::DnsRecordType;
|
use super::DnsRecordType;
|
||||||
use super::DnsServer;
|
use super::DnsServer;
|
||||||
use super::Firewall;
|
use super::Firewall;
|
||||||
|
use super::HostNetworkConfig;
|
||||||
use super::HttpServer;
|
use super::HttpServer;
|
||||||
use super::IpAddress;
|
use super::IpAddress;
|
||||||
use super::K8sclient;
|
use super::K8sclient;
|
||||||
@ -24,6 +26,8 @@ use super::LogicalHost;
|
|||||||
use super::PreparationError;
|
use super::PreparationError;
|
||||||
use super::PreparationOutcome;
|
use super::PreparationOutcome;
|
||||||
use super::Router;
|
use super::Router;
|
||||||
|
use super::Switch;
|
||||||
|
use super::SwitchError;
|
||||||
use super::TftpServer;
|
use super::TftpServer;
|
||||||
|
|
||||||
use super::Topology;
|
use super::Topology;
|
||||||
@ -263,6 +267,21 @@ impl HttpServer for HAClusterTopology {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Switch for HAClusterTopology {
|
||||||
|
async fn get_port_for_mac_address(&self, mac_address: &MacAddress) -> Option<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_host_network(
|
||||||
|
&self,
|
||||||
|
_host: &PhysicalHost,
|
||||||
|
_config: HostNetworkConfig,
|
||||||
|
) -> Result<(), SwitchError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DummyInfra;
|
pub struct DummyInfra;
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::{net::Ipv4Addr, str::FromStr, sync::Arc};
|
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 harmony_types::net::{IpAddress, MacAddress};
|
use harmony_types::net::{IpAddress, MacAddress};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::executors::ExecutorError;
|
use crate::{executors::ExecutorError, hardware::PhysicalHost};
|
||||||
|
|
||||||
use super::{LogicalHost, k8s::K8sClient};
|
use super::{LogicalHost, k8s::K8sClient};
|
||||||
|
|
||||||
@ -172,6 +173,46 @@ impl FromStr for DnsRecordType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Switch: Send + Sync {
|
||||||
|
async fn get_port_for_mac_address(&self, mac_address: &MacAddress) -> Option<String>;
|
||||||
|
|
||||||
|
async fn configure_host_network(
|
||||||
|
&self,
|
||||||
|
_host: &PhysicalHost,
|
||||||
|
_config: HostNetworkConfig,
|
||||||
|
) -> Result<(), SwitchError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct HostNetworkConfig {
|
||||||
|
pub bond: Bond,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Bond {
|
||||||
|
pub interfaces: Vec<SlaveInterface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct SlaveInterface {
|
||||||
|
pub mac_address: MacAddress,
|
||||||
|
// FIXME: Should we add speed as well? And other params
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, new)]
|
||||||
|
pub struct SwitchError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SwitchError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for SwitchError {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -5,11 +5,13 @@ use crate::{
|
|||||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
inventory::{HostRole, Inventory},
|
inventory::{HostRole, Inventory},
|
||||||
modules::{
|
modules::{
|
||||||
dhcp::DhcpHostBindingScore, http::IPxeMacBootFileScore,
|
dhcp::DhcpHostBindingScore,
|
||||||
inventory::DiscoverHostForRoleScore, okd::templates::BootstrapIpxeTpl,
|
http::IPxeMacBootFileScore,
|
||||||
|
inventory::DiscoverHostForRoleScore,
|
||||||
|
okd::{host_network::HostNetworkConfigurationScore, templates::BootstrapIpxeTpl},
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{HAClusterTopology, HostBinding},
|
topology::{self, HAClusterTopology, HostBinding},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
@ -209,8 +211,23 @@ impl OKDSetup03ControlPlaneInterpret {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Apply host network configuration.
|
||||||
|
// Delegate to a score: HostNetworkConfigurationScore { host: physical_host } qui manipule Switch dans Topology
|
||||||
|
// Use-case Affilium: remplacement carte reseau, pas juste installation clean
|
||||||
|
//
|
||||||
/// Placeholder for automating network bonding configuration.
|
/// Placeholder for automating network bonding configuration.
|
||||||
async fn persist_network_bond(&self) -> Result<(), InterpretError> {
|
async fn persist_network_bond(
|
||||||
|
&self,
|
||||||
|
inventory: &Inventory,
|
||||||
|
topology: &HAClusterTopology,
|
||||||
|
hosts: &Vec<PhysicalHost>,
|
||||||
|
) -> Result<(), InterpretError> {
|
||||||
|
let score = HostNetworkConfigurationScore {
|
||||||
|
hosts: hosts.clone(), // FIXME: Avoid clone if possible
|
||||||
|
};
|
||||||
|
|
||||||
|
score.interpret(inventory, topology);
|
||||||
|
|
||||||
// Generate MC or NNCP from inventory NIC data; apply via ignition or post-join.
|
// Generate MC or NNCP from inventory NIC data; apply via ignition or post-join.
|
||||||
info!("[ControlPlane] Ensuring persistent bonding via MachineConfig/NNCP");
|
info!("[ControlPlane] Ensuring persistent bonding via MachineConfig/NNCP");
|
||||||
inquire::Confirm::new(
|
inquire::Confirm::new(
|
||||||
@ -260,7 +277,8 @@ impl Interpret<HAClusterTopology> for OKDSetup03ControlPlaneInterpret {
|
|||||||
self.reboot_targets(&nodes).await?;
|
self.reboot_targets(&nodes).await?;
|
||||||
|
|
||||||
// 5. Placeholder for post-boot network configuration (e.g., bonding).
|
// 5. Placeholder for post-boot network configuration (e.g., bonding).
|
||||||
self.persist_network_bond().await?;
|
self.persist_network_bond(inventory, topology, &nodes)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// TODO: Implement a step to wait for the control plane nodes to join the cluster
|
// TODO: Implement a step to wait for the control plane nodes to join the cluster
|
||||||
// and for the cluster operators to become available. This would be similar to
|
// and for the cluster operators to become available. This would be similar to
|
||||||
|
205
harmony/src/modules/okd/host_network.rs
Normal file
205
harmony/src/modules/okd/host_network.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::{id::Id, net::MacAddress};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::Version,
|
||||||
|
hardware::PhysicalHost,
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
score::Score,
|
||||||
|
topology::{self, Bond, HostNetworkConfig, SlaveInterface, Switch, Topology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct HostNetworkConfigurationScore {
|
||||||
|
pub hosts: Vec<PhysicalHost>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + Switch> Score<T> for HostNetworkConfigurationScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"HostNetworkConfigurationScore".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(HostNetworkConfigurationInterpret {
|
||||||
|
score: self.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HostNetworkConfigurationInterpret {
|
||||||
|
score: HostNetworkConfigurationScore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + Switch> Interpret<T> for HostNetworkConfigurationInterpret {
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("HostNetworkConfigurationInterpret")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
_inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let host = self.score.hosts.first().unwrap();
|
||||||
|
let mac_addresses = host.get_mac_address();
|
||||||
|
let mac_address = mac_addresses.first().unwrap();
|
||||||
|
let host_network_config = HostNetworkConfig {
|
||||||
|
bond: Bond {
|
||||||
|
interfaces: vec![SlaveInterface {
|
||||||
|
mac_address: *mac_address,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = topology
|
||||||
|
.configure_host_network(host, host_network_config)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// foreach hosts
|
||||||
|
// foreach mac addresses
|
||||||
|
// let port = topology.get_port_for_mac_address(); // si pas de port -> mac address pas connectee
|
||||||
|
// create port channel for all ports found
|
||||||
|
// create bond for all valid addresses (port found)
|
||||||
|
// apply network to host first, then switch (to avoid losing hosts that are already connected)
|
||||||
|
// topology.configure_host_network(host, config) <--- will create bonds
|
||||||
|
// topology.configure_switch_network(port, config) <--- will create port channels
|
||||||
|
|
||||||
|
Ok(Outcome::success("".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PortMapping {
|
||||||
|
port: String,
|
||||||
|
mac_address: MacAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assertor::*;
|
||||||
|
use harmony_inventory_agent::hwinfo::NetworkInterface;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hardware::HostCategory,
|
||||||
|
topology::{
|
||||||
|
Bond, HostNetworkConfig, PreparationError, PreparationOutcome, SlaveInterface,
|
||||||
|
SwitchError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
str::FromStr,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref HOST_ID: Id = Id::from_str("host-1").unwrap();
|
||||||
|
pub static ref INTERFACE: MacAddress =
|
||||||
|
MacAddress::try_from("00:11:22:33:44:55".to_string()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn one_host_one_mac_address_should_create_bond_with_one_interface() {
|
||||||
|
let host = given_host(&HOST_ID, *INTERFACE);
|
||||||
|
let score = given_score(vec![host]);
|
||||||
|
let topology = SwitchWithPortTopology::new();
|
||||||
|
|
||||||
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
||||||
|
|
||||||
|
let configured_host_networks = topology.configured_host_networks.lock().unwrap();
|
||||||
|
assert_that!(*configured_host_networks).contains_exactly(vec![(
|
||||||
|
HOST_ID.clone(),
|
||||||
|
HostNetworkConfig {
|
||||||
|
bond: Bond {
|
||||||
|
interfaces: vec![SlaveInterface {
|
||||||
|
mac_address: *INTERFACE,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn given_host(id: &Id, mac_address: MacAddress) -> PhysicalHost {
|
||||||
|
PhysicalHost {
|
||||||
|
id: id.clone(),
|
||||||
|
category: HostCategory::Server,
|
||||||
|
network: vec![NetworkInterface {
|
||||||
|
name: "interface-1".into(),
|
||||||
|
mac_address,
|
||||||
|
speed_mbps: None,
|
||||||
|
is_up: true,
|
||||||
|
mtu: 1,
|
||||||
|
ipv4_addresses: vec![],
|
||||||
|
ipv6_addresses: vec![],
|
||||||
|
driver: "driver".into(),
|
||||||
|
firmware_version: None,
|
||||||
|
}],
|
||||||
|
storage: vec![],
|
||||||
|
labels: vec![],
|
||||||
|
memory_modules: vec![],
|
||||||
|
cpus: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn given_score(hosts: Vec<PhysicalHost>) -> HostNetworkConfigurationScore {
|
||||||
|
HostNetworkConfigurationScore { hosts }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwitchWithPortTopology {
|
||||||
|
configured_host_networks: Arc<Mutex<Vec<(Id, HostNetworkConfig)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchWithPortTopology {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
configured_host_networks: Arc::new(Mutex::new(vec![])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Topology for SwitchWithPortTopology {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"SwitchWithPortTopology"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
|
||||||
|
Ok(PreparationOutcome::Success { details: "".into() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Switch for SwitchWithPortTopology {
|
||||||
|
async fn get_port_for_mac_address(&self, mac_address: &MacAddress) -> Option<String> {
|
||||||
|
Some("1/0/42".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn configure_host_network(
|
||||||
|
&self,
|
||||||
|
host: &PhysicalHost,
|
||||||
|
config: HostNetworkConfig,
|
||||||
|
) -> Result<(), SwitchError> {
|
||||||
|
let mut configured_host_networks = self.configured_host_networks.lock().unwrap();
|
||||||
|
configured_host_networks.push((host.id.clone(), config.clone()));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,3 +19,4 @@ pub use bootstrap_03_control_plane::*;
|
|||||||
pub use bootstrap_04_workers::*;
|
pub use bootstrap_04_workers::*;
|
||||||
pub use bootstrap_05_sanity_check::*;
|
pub use bootstrap_05_sanity_check::*;
|
||||||
pub use bootstrap_06_installation_report::*;
|
pub use bootstrap_06_installation_report::*;
|
||||||
|
pub mod host_network;
|
||||||
|
Loading…
Reference in New Issue
Block a user