WIP: configure-switch #159
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",
|
||||||
|
14
Cargo.toml
14
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]
|
||||||
@ -66,5 +67,12 @@ thiserror = "2.0.14"
|
|||||||
serde = { version = "1.0.209", features = ["derive", "rc"] }
|
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
|
|||||||
|
|
||||||
letian marked this conversation as resolved
Outdated
|
|||||||
[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>,
|
||||||
|
}
|
||||||
|
|
||||||
letian marked this conversation as resolved
johnride
commented
speed yes, maybe we will need something else but nothing comes to mind right now. speed yes, maybe we will need something else but nothing comes to mind right now.
|
|||||||
|
#[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.
|
||||||
johnride
commented
The clone has been bugging me too. We could use an Rc or Arc too as a balance between usability and performance. The clone has been bugging me too. We could use an Rc or Arc too as a balance between usability and performance.
|
|||||||
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
Je ne met pas la version dans les dependances locales habituellement