feat(bootstrapping): add bootstrap load balancer and DHCP configurations

- Introduce `bootstrap_load_balancer` module for handling initial load balancing configuration.
- Add `bootstrap_dhcp` module for bootstrapping DHCP settings.
- Create `harmony_types` crate to house shared types, including `MacAddress`.
- Update `harmony_macros` to use `harmony_types` instead of directly referencing `harmony`.
This commit is contained in:
jeangab 2025-01-09 11:58:49 -05:00
parent a80ead418e
commit bec96c2954
20 changed files with 208 additions and 55 deletions

8
harmony-rs/Cargo.lock generated
View File

@ -893,6 +893,8 @@ dependencies = [
"cidr",
"derive-new",
"env_logger",
"harmony_macros",
"harmony_types",
"libredfish",
"log",
"opnsense-config",
@ -912,11 +914,15 @@ dependencies = [
name = "harmony_macros"
version = "1.0.0"
dependencies = [
"harmony",
"harmony_types",
"quote",
"syn 2.0.90",
]
[[package]]
name = "harmony_types"
version = "1.0.0"
[[package]]
name = "hashbrown"
version = "0.14.5"

View File

@ -4,7 +4,10 @@ members = [
"private_repos/*",
"demo/*",
"harmony",
"opnsense-config", "opnsense-config-xml", "harmony_macros",
"harmony_types",
"harmony_macros",
"opnsense-config",
"opnsense-config-xml",
]
[workspace.package]

View File

@ -10,7 +10,9 @@ use harmony::{
inventory::Inventory,
maestro::Maestro,
modules::{
http::HttpScore, okd::{dhcp::OKDBootstrapDhcpScore, dns::OKDBootstrapDnsScore}, tftp::TftpScore
http::HttpScore,
okd::{dhcp::OKDDhcpScore, dns::OKDDnsScore},
tftp::TftpScore,
},
topology::{LogicalHost, UnmanagedRouter, Url},
};
@ -48,6 +50,10 @@ async fn main() {
ip: ip!("10.100.8.20"),
name: "cp0".to_string(),
}],
bootstrap_host: LogicalHost {
ip: ip!("10.100.8.20"),
name: "cp0".to_string(),
},
workers: vec![],
switch: vec![],
};
@ -73,8 +79,8 @@ async fn main() {
// TODO regroup smaller scores in a larger one such as this
// let okd_boostrap_preparation();
// let dhcp_score = OKDBootstrapDhcpScore::new(&topology, &inventory);
// let dns_score = OKDBootstrapDnsScore::new(&topology);
// let dhcp_score = OKDDhcpScore::new(&topology, &inventory);
// let dns_score = OKDDnsScore::new(&topology);
// let load_balancer_score =
// harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology);

View File

@ -19,5 +19,7 @@ async-trait = { workspace = true }
cidr = { workspace = true }
opnsense-config = { path = "../opnsense-config" }
opnsense-config-xml = { path = "../opnsense-config-xml" }
harmony_macros = { path = "../harmony_macros" }
harmony_types = { path = "../harmony_types" }
uuid = { workspace = true }
url = { workspace = true }

View File

@ -1,8 +1,8 @@
use std::sync::Arc;
use derive_new::new;
use harmony_types::net::MacAddress;
use crate::topology::MacAddress;
pub type HostGroup = Vec<PhysicalHost>;
pub type SwitchGroup = Vec<Switch>;

View File

@ -1,15 +1,15 @@
mod host_binding;
mod http;
mod load_balancer;
mod router;
mod tftp;
mod http;
pub use load_balancer::*;
pub use router::*;
mod network;
pub use host_binding::*;
pub use http::*;
pub use network::*;
pub use tftp::*;
pub use http::*;
use std::{net::IpAddr, sync::Arc};
@ -23,6 +23,7 @@ pub struct HAClusterTopology {
pub tftp_server: Arc<dyn TftpServer>,
pub http_server: Arc<dyn HttpServer>,
pub dns_server: Arc<dyn DnsServer>,
pub bootstrap_host: LogicalHost,
pub control_plane: Vec<LogicalHost>,
pub workers: Vec<LogicalHost>,
pub switch: Vec<LogicalHost>,

View File

@ -1,6 +1,7 @@
use std::{error::Error, net::Ipv4Addr, str::FromStr};
use async_trait::async_trait;
use harmony_types::net::MacAddress;
use crate::executors::ExecutorError;
@ -117,35 +118,6 @@ pub enum Action {
Deny,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MacAddress(pub [u8; 6]);
impl MacAddress {
#[cfg(test)]
pub fn dummy() -> Self {
Self([0, 0, 0, 0, 0, 0])
}
}
impl From<&MacAddress> for String {
fn from(value: &MacAddress) -> Self {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5]
)
}
}
impl std::fmt::Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"MacAddress {}",
String::from(self)
))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DnsRecordType {
A,

View File

@ -1,9 +1,12 @@
use crate::{hardware::ManagementInterface, topology::{IpAddress, MacAddress}};
use crate::hardware::ManagementInterface;
use crate::topology::IpAddress;
use derive_new::new;
use harmony_types::net::MacAddress;
#[derive(new)]
pub struct HPIlo {
ip_address: IpAddress,
ip_address: Option<IpAddress>,
mac_address: Option<MacAddress>,
}
impl ManagementInterface for HPIlo {

View File

@ -1,8 +1,6 @@
use crate::{
hardware::ManagementInterface,
topology::{IpAddress, MacAddress},
};
use crate::hardware::ManagementInterface;
use derive_new::new;
use harmony_types::net::MacAddress;
#[derive(new)]
pub struct IntelAmtManagement {

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use harmony_types::net::MacAddress;
use log::debug;
use crate::{executors::ExecutorError, topology::{DHCPStaticEntry, DhcpServer, IpAddress, LogicalHost}};
@ -28,12 +29,12 @@ impl DhcpServer for OPNSenseFirewall {
async fn remove_static_mapping(
&self,
_mac: &crate::topology::MacAddress,
_mac: &MacAddress,
) -> Result<(), ExecutorError> {
todo!()
}
async fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> {
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)> {
todo!()
}

View File

@ -1,6 +1,5 @@
use derive_new::new;
use crate::{hardware::ManagementInterface, topology::MacAddress};
use crate::hardware::ManagementInterface;
#[derive(new)]
pub struct OPNSenseManagementInterface {}

View File

@ -0,0 +1,55 @@
use crate::{
inventory::Inventory,
modules::dhcp::DhcpScore,
score::Score,
topology::{HAClusterTopology, HostBinding, LogicalHost},
};
use harmony_macros::ip;
#[derive(Debug)]
pub struct OKDBootstrapDhcpScore {
dhcp_score: DhcpScore,
}
impl OKDBootstrapDhcpScore {
pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self {
let mut host_binding: Vec<_> = topology
.control_plane
.iter()
.enumerate()
.map(|(index, topology_entry)| HostBinding {
logical_host: topology_entry.clone(),
physical_host: inventory
.control_plane_host
.get(index)
.expect("Iventory should contain at least as many physical hosts as topology")
.clone(),
})
.collect();
host_binding.push(HostBinding {
logical_host: topology.bootstrap_host.clone(),
physical_host: inventory
.worker_host
.get(0)
.expect("Should have at least one worker to be used as bootstrap node")
.clone(),
});
Self {
dhcp_score: DhcpScore::new(
host_binding,
// TODO : we should add a tftp server to the topology instead of relying on the
// router address, this is leaking implementation details
Some(topology.router.get_gateway()),
Some("bootx64.efi".to_string()),
),
}
}
}
impl Score for OKDBootstrapDhcpScore {
type InterpretType = <DhcpScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {
self.dhcp_score.create_interpret()
}
}

View File

@ -0,0 +1,76 @@
use std::net::SocketAddr;
use crate::{
modules::load_balancer::LoadBalancerScore,
score::Score,
topology::{
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode,
LoadBalancerService,
},
};
#[derive(Debug)]
pub struct OKDBootstrapLoadBalancerScore {
load_balancer_score: LoadBalancerScore,
}
impl OKDBootstrapLoadBalancerScore {
pub fn new(topology: &HAClusterTopology) -> Self {
let private_ip = topology.router.get_gateway();
let bootstrap_host = &topology.bootstrap_host;
let private_services = vec![
LoadBalancerService {
backend_servers: vec![BackendServer {
address: bootstrap_host.ip.to_string(),
port: 80,
}],
listening_port: SocketAddr::new(private_ip, 80),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: vec![BackendServer {
address: bootstrap_host.ip.to_string(),
port: 443,
}],
listening_port: SocketAddr::new(private_ip, 443),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: vec![BackendServer {
address: bootstrap_host.ip.to_string(),
port: 22623,
}],
listening_port: SocketAddr::new(private_ip, 22623),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: vec![BackendServer {
address: bootstrap_host.ip.to_string(),
port: 6443,
}],
listening_port: SocketAddr::new(private_ip, 6443),
health_check: Some(HealthCheck::HTTP(
"/readyz".to_string(),
HttpMethod::GET,
HttpStatusCode::Success2xx,
)),
},
];
Self {
load_balancer_score: LoadBalancerScore {
public_services: vec![],
private_services,
},
}
}
}
impl Score for OKDBootstrapLoadBalancerScore {
type InterpretType = <LoadBalancerScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {
self.load_balancer_score.create_interpret()
}
}

View File

@ -6,11 +6,11 @@ use crate::{
};
#[derive(Debug)]
pub struct OKDBootstrapDhcpScore {
pub struct OKDDhcpScore {
dhcp_score: DhcpScore,
}
impl OKDBootstrapDhcpScore {
impl OKDDhcpScore {
pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self {
let host_binding = topology
.control_plane
@ -37,7 +37,7 @@ impl OKDBootstrapDhcpScore {
}
}
impl Score for OKDBootstrapDhcpScore {
impl Score for OKDDhcpScore {
type InterpretType = <DhcpScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {

View File

@ -5,11 +5,11 @@ use crate::{
};
#[derive(Debug)]
pub struct OKDBootstrapDnsScore {
pub struct OKDDnsScore {
dns_score: DnsScore,
}
impl OKDBootstrapDnsScore {
impl OKDDnsScore {
pub fn new(topology: &HAClusterTopology) -> Self {
let cluster_domain_name = &topology.domain_name;
let dns_entries = vec![
@ -39,7 +39,7 @@ impl OKDBootstrapDnsScore {
}
}
impl Score for OKDBootstrapDnsScore {
impl Score for OKDDnsScore {
type InterpretType = <DnsScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {

View File

@ -1,4 +1,6 @@
pub mod dhcp;
pub mod dns;
pub mod load_balancer;
pub mod bootstrap_load_balancer;
pub mod bootstrap_dhcp;

View File

@ -7,6 +7,6 @@ version = "1.0.0"
proc-macro = true
[dependencies]
harmony = { version = "0.1.0", path = "../harmony" }
harmony_types = { path = "../harmony_types" }
quote = "1.0.37"
syn = "2.0.90"

View File

@ -1,6 +1,5 @@
extern crate proc_macro;
use harmony::topology::MacAddress;
use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};

View File

@ -0,0 +1,4 @@
[package]
name = "harmony_types"
edition = "2024"
version = "1.0.0"

View File

@ -0,0 +1,26 @@
pub mod net {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MacAddress(pub [u8; 6]);
impl MacAddress {
#[cfg(test)]
pub fn dummy() -> Self {
Self([0, 0, 0, 0, 0, 0])
}
}
impl From<&MacAddress> for String {
fn from(value: &MacAddress) -> Self {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5]
)
}
}
impl std::fmt::Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("MacAddress {}", String::from(self)))
}
}
}