feat: Topology, inventory, score, its all coming together and compiles. I think its very usable as a first alpha version. Game on

This commit is contained in:
Jean-Gabriel Gill-Couture 2024-09-08 22:42:29 -04:00
parent ff3d53c704
commit df714e216a
13 changed files with 105 additions and 48 deletions

View File

@ -1,7 +1,6 @@
use fqm::{inventory::get_inventory, topology::fqm_topology}; use fqm::{inventory::get_inventory, topology::fqm_topology};
use harmony::{ use harmony::{
inventory::InventoryFilter, maestro::Maestro,
maestro::Maestro,
modules::opnsense_dhcp::OPNSenseDhcpScore, modules::opnsense_dhcp::OPNSenseDhcpScore,
}; };
use log::info; use log::info;
@ -12,9 +11,9 @@ async fn main() {
tokio::spawn(async move { tokio::spawn(async move {
info!("FQM Harmony Starting"); info!("FQM Harmony Starting");
let maestro = Maestro::new(get_inventory());
let topology = fqm_topology(); let topology = fqm_topology();
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![])); let maestro = Maestro::new(get_inventory(), topology.clone());
let score = OPNSenseDhcpScore::new(topology);
let result = maestro.interpret(score).await.unwrap(); let result = maestro.interpret(score).await.unwrap();
info!("{result}"); info!("{result}");
}).await.unwrap(); }).await.unwrap();

View File

@ -1,18 +1,19 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::Arc;
use cidr::Ipv4Cidr; use cidr::Ipv4Cidr;
use harmony::infra::opnsense::OPNSenseFirewall; use harmony::infra::opnsense::OPNSenseFirewall;
use harmony::topology::{HAClusterTopology, IpAddress, UnmanagedRouter}; use harmony::topology::{HAClusterTopology, IpAddress, UnmanagedRouter};
pub fn fqm_topology() -> HAClusterTopology { pub fn fqm_topology() -> HAClusterTopology {
let opnsense_firewall = Box::new(OPNSenseFirewall::new(IpAddress::V4(Ipv4Addr::new(10, 10, 8, 2)))); let opnsense_firewall = Arc::new(OPNSenseFirewall::new(IpAddress::V4(Ipv4Addr::new(10, 10, 8, 2))));
HAClusterTopology { HAClusterTopology {
firewall: opnsense_firewall.clone(), firewall: opnsense_firewall.clone(),
control_plane: vec![], control_plane: vec![],
ceph_hosts: vec![], ceph_hosts: vec![],
switch: vec![], switch: vec![],
router: Box::new(UnmanagedRouter::new( router: Arc::new(UnmanagedRouter::new(
IpAddress::V4(Ipv4Addr::new(10, 10, 8, 1)), IpAddress::V4(Ipv4Addr::new(10, 10, 8, 1)),
Ipv4Cidr::new(Ipv4Addr::new(10, 10, 8, 0), 22).expect("Subnet is valid"), Ipv4Cidr::new(Ipv4Addr::new(10, 10, 8, 0), 22).expect("Subnet is valid"),
)), )),

View File

@ -3,7 +3,7 @@ use derive_new::new;
pub type HostGroup = Vec<Host>; pub type HostGroup = Vec<Host>;
pub type SwitchGroup = Vec<Switch>; pub type SwitchGroup = Vec<Switch>;
pub type FirewallGroup = Vec<Host>; pub type FirewallGroup = Vec<Host>;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Host { pub struct Host {
pub category: HostCategory, pub category: HostCategory,
pub network: Vec<NetworkInterface>, pub network: Vec<NetworkInterface>,
@ -11,14 +11,14 @@ pub struct Host {
pub labels: Vec<Label>, pub labels: Vec<Label>,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum HostCategory { pub enum HostCategory {
Server, Server,
Firewall, Firewall,
Switch, Switch,
} }
#[derive(Debug, new)] #[derive(Debug, new, Clone)]
pub struct NetworkInterface { pub struct NetworkInterface {
name: String, name: String,
mac_address: MacAddress, mac_address: MacAddress,
@ -27,7 +27,7 @@ pub struct NetworkInterface {
} }
type MacAddress = String; type MacAddress = String;
#[derive(Debug, new)] #[derive(Debug, new, Clone)]
pub enum StorageConnectionType { pub enum StorageConnectionType {
Sata3g, Sata3g,
Sata6g, Sata6g,
@ -35,13 +35,13 @@ pub enum StorageConnectionType {
Sas12g, Sas12g,
PCIE, PCIE,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum StorageKind { pub enum StorageKind {
SSD, SSD,
NVME, NVME,
HDD, HDD,
} }
#[derive(Debug, new)] #[derive(Debug, new, Clone)]
pub struct Storage { pub struct Storage {
connection: StorageConnectionType, connection: StorageConnectionType,
kind: StorageKind, kind: StorageKind,
@ -49,13 +49,13 @@ pub struct Storage {
serial: String, serial: String,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Switch { pub struct Switch {
interface: Vec<NetworkInterface>, interface: Vec<NetworkInterface>,
management_interface: NetworkInterface, management_interface: NetworkInterface,
} }
#[derive(Debug, new)] #[derive(Debug, new, Clone)]
pub struct Label { pub struct Label {
name: String, name: String,
value: String, value: String,

View File

@ -4,7 +4,7 @@ use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use super::{ use super::{
data::{Id, Version}, executors::ExecutorError, inventory::Inventory, score::Score data::{Id, Version}, executors::ExecutorError, inventory::Inventory, score::Score, topology::HAClusterTopology
}; };
pub enum InterpretName { pub enum InterpretName {
@ -21,7 +21,7 @@ impl std::fmt::Display for InterpretName {
#[async_trait] #[async_trait]
pub trait Interpret { pub trait Interpret {
async fn execute(&self, inventory: &Inventory) -> Result<Outcome, InterpretError>; async fn execute(&self, inventory: &Inventory, topology: &HAClusterTopology) -> Result<Outcome, InterpretError>;
fn get_name(&self) -> InterpretName; fn get_name(&self) -> InterpretName;
fn get_version(&self) -> Version; fn get_version(&self) -> Version;
fn get_status(&self) -> InterpretStatus; fn get_status(&self) -> InterpretStatus;

View File

@ -1,11 +1,12 @@
use derive_new::new; use derive_new::new;
use log::info; use log::info;
use super::{interpret::{Interpret, InterpretError, Outcome}, inventory::Inventory, score::Score}; use super::{interpret::{Interpret, InterpretError, Outcome}, inventory::Inventory, score::Score, topology::HAClusterTopology};
#[derive(new)] #[derive(new)]
pub struct Maestro { pub struct Maestro {
inventory: Inventory, inventory: Inventory,
topology: HAClusterTopology,
} }
impl Maestro { impl Maestro {
@ -32,7 +33,7 @@ impl Maestro {
info!("Running score {score:?}"); info!("Running score {score:?}");
let interpret: S::InterpretType = score.create_interpret(); let interpret: S::InterpretType = score.create_interpret();
info!("Launching interpret {interpret:?}"); info!("Launching interpret {interpret:?}");
let result = interpret.execute(&self.inventory).await; let result = interpret.execute(&self.inventory, &self.topology).await;
info!("Got result {result:?}"); info!("Got result {result:?}");
result result
} }

View File

@ -1,6 +1,6 @@
use super::{interpret::Interpret, inventory::InventorySlice}; use super::{interpret::Interpret, inventory::InventorySlice};
pub trait Score: Send + Sync + std::fmt::Debug { pub trait Score: std::fmt::Debug {
type InterpretType: Interpret + std::fmt::Debug; type InterpretType: Interpret + std::fmt::Debug;
fn get_inventory_filter(&self) -> InventorySlice; fn get_inventory_filter(&self) -> InventorySlice;
fn create_interpret(self) -> Self::InterpretType; fn create_interpret(self) -> Self::InterpretType;

View File

@ -1,13 +1,21 @@
use super::IpAddress; use super::IpAddress;
pub trait LoadBalancer { pub trait LoadBalancer : Send + Sync{
fn add_backend(&mut self, backend: Backend) -> Result<(), LoadBalancerError>; fn add_backend(&mut self, backend: Backend) -> Result<(), LoadBalancerError>;
fn remove_backend(&mut self, backend_id: &str) -> Result<(), LoadBalancerError>; fn remove_backend(&mut self, backend_id: &str) -> Result<(), LoadBalancerError>;
fn add_frontend(&mut self, frontend: Frontend) -> Result<(), LoadBalancerError>; fn add_frontend(&mut self, frontend: Frontend) -> Result<(), LoadBalancerError>;
fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), LoadBalancerError>; fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), LoadBalancerError>;
fn list_backends(&self) -> Vec<Backend>; fn list_backends(&self) -> Vec<Backend>;
fn list_frontends(&self) -> Vec<Frontend>; fn list_frontends(&self) -> Vec<Frontend>;
fn get_ip(&self) -> IpAddress;
} }
impl std::fmt::Debug for dyn LoadBalancer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("LoadBalancer {}", self.get_ip()))
}
}
pub struct LoadBalancerError; pub struct LoadBalancerError;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -1,20 +1,21 @@
mod router;
mod load_balancer; mod load_balancer;
pub use router::*; mod router;
pub use load_balancer::*; pub use load_balancer::*;
pub use router::*;
mod network; mod network;
pub use network::*; pub use network::*;
use std::net::IpAddr; use std::{net::IpAddr, sync::Arc};
use super::hardware::Host; use super::hardware::Host;
#[derive(Debug, Clone)]
pub struct HAClusterTopology { pub struct HAClusterTopology {
pub router: Box<dyn Router + Send>, pub router: Arc<dyn Router>,
pub load_balancer: Box<dyn LoadBalancer + Send>, pub load_balancer: Arc<dyn LoadBalancer>,
pub firewall: Box<dyn Firewall + Send>, pub firewall: Arc<dyn Firewall>,
pub dhcp_server: Box<dyn DhcpServer + Send>, pub dhcp_server: Arc<dyn DhcpServer>,
pub dns_server: Box<dyn DnsServer + Send>, pub dns_server: Arc<dyn DnsServer>,
pub control_plane: Vec<ClusterMember>, pub control_plane: Vec<ClusterMember>,
pub workers: Vec<ClusterMember>, pub workers: Vec<ClusterMember>,
pub ceph_hosts: Vec<ClusterMember>, pub ceph_hosts: Vec<ClusterMember>,
@ -23,13 +24,22 @@ pub struct HAClusterTopology {
pub type IpAddress = IpAddr; pub type IpAddress = IpAddr;
#[derive(Debug, Clone)]
pub struct ClusterMember { pub struct ClusterMember {
pub management: Box<dyn ManagementInterface + Send>, pub management: Arc<dyn ManagementInterface>,
pub host: Host, pub host: Host,
} }
pub trait ManagementInterface { pub trait ManagementInterface: Send + Sync {
fn boot_to_pxe(&self); fn boot_to_pxe(&self);
fn get_ip(&self) -> IpAddress; fn get_ip(&self) -> IpAddress;
} }
impl std::fmt::Debug for dyn ManagementInterface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"ManagementInterface with ip {}",
self.get_ip()
))
}
}

View File

@ -1,28 +1,47 @@
use super::IpAddress; use super::IpAddress;
pub trait Firewall { pub trait Firewall: Send + Sync {
fn add_rule(&mut self, rule: FirewallRule) -> Result<(), FirewallError>; fn add_rule(&mut self, rule: FirewallRule) -> Result<(), FirewallError>;
fn remove_rule(&mut self, rule_id: &str) -> Result<(), FirewallError>; fn remove_rule(&mut self, rule_id: &str) -> Result<(), FirewallError>;
fn list_rules(&self) -> Vec<FirewallRule>; fn list_rules(&self) -> Vec<FirewallRule>;
fn get_ip(&self) -> IpAddress; fn get_ip(&self) -> IpAddress;
} }
impl std::fmt::Debug for dyn Firewall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Firewall {}", self.get_ip()))
}
}
pub struct NetworkDomain { pub struct NetworkDomain {
pub name: String pub name: String
} }
pub trait DhcpServer { pub trait DhcpServer: Send + Sync {
fn add_static_mapping(&mut self, mac: MacAddress, ip: IpAddress) -> Result<(), DhcpError>; fn add_static_mapping(&mut self, mac: MacAddress, ip: IpAddress) -> Result<(), DhcpError>;
fn remove_static_mapping(&mut self, mac: &MacAddress) -> Result<(), DhcpError>; fn remove_static_mapping(&mut self, mac: &MacAddress) -> Result<(), DhcpError>;
fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>; fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
fn get_ip(&self) -> IpAddress;
} }
pub trait DnsServer { impl std::fmt::Debug for dyn DhcpServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("DhcpServer {}", self.get_ip()))
}
}
pub trait DnsServer: Send + Sync {
fn add_record(&mut self, name: &str, record_type: DnsRecordType, value: &str) -> Result<(), DnsError>; fn add_record(&mut self, name: &str, record_type: DnsRecordType, value: &str) -> Result<(), DnsError>;
fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), DnsError>; fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), DnsError>;
fn list_records(&self) -> Vec<DnsRecord>; fn list_records(&self) -> Vec<DnsRecord>;
fn get_ip(&self) -> IpAddress;
} }
impl std::fmt::Debug for dyn DnsServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("DnsServer {}", self.get_ip()))
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FirewallRule { pub struct FirewallRule {
pub id: String, pub id: String,

View File

@ -1,13 +1,21 @@
use std::sync::Arc;
use cidr::Ipv4Cidr; use cidr::Ipv4Cidr;
use derive_new::new; use derive_new::new;
use super::IpAddress; use super::IpAddress;
pub trait Router { pub trait Router: Send + Sync {
fn get_gateway(&self) -> IpAddress; fn get_gateway(&self) -> IpAddress;
fn get_cidr(&self) -> Ipv4Cidr; fn get_cidr(&self) -> Ipv4Cidr;
} }
impl std::fmt::Debug for dyn Router {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Router Gateway : {}, CIDR : {}", self.get_gateway(), self.get_cidr()))
}
}
#[derive(new)] #[derive(new)]
pub struct UnmanagedRouter { pub struct UnmanagedRouter {
gateway: IpAddress, gateway: IpAddress,

View File

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use log::info;
use russh::{client, keys::key}; use russh::{client, keys::key};
use crate::domain::executors::{ExecutorError, SshClient}; use crate::domain::executors::{ExecutorError, SshClient};

View File

@ -48,31 +48,43 @@ impl LoadBalancer for OPNSenseFirewall {
fn list_frontends(&self) -> Vec<Frontend> { fn list_frontends(&self) -> Vec<Frontend> {
todo!() todo!()
} }
fn get_ip(&self) -> IpAddress {
self.ip_address.clone()
}
} }
impl DhcpServer for OPNSenseFirewall { impl DhcpServer for OPNSenseFirewall {
fn add_static_mapping(&mut self, mac: crate::topology::MacAddress, ip: IpAddress) -> Result<(), crate::topology::DhcpError> { fn add_static_mapping(&mut self, _mac: crate::topology::MacAddress, _ip: IpAddress) -> Result<(), crate::topology::DhcpError> {
todo!() todo!()
} }
fn remove_static_mapping(&mut self, mac: &crate::topology::MacAddress) -> Result<(), crate::topology::DhcpError> { fn remove_static_mapping(&mut self, _mac: &crate::topology::MacAddress) -> Result<(), crate::topology::DhcpError> {
todo!() todo!()
} }
fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> { fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> {
todo!() todo!()
} }
fn get_ip(&self) -> IpAddress {
self.ip_address.clone()
}
} }
impl DnsServer for OPNSenseFirewall { impl DnsServer for OPNSenseFirewall {
fn add_record(&mut self, name: &str, record_type: crate::topology::DnsRecordType, value: &str) -> Result<(), crate::topology::DnsError> { fn add_record(&mut self, _name: &str, _record_type: crate::topology::DnsRecordType, _value: &str) -> Result<(), crate::topology::DnsError> {
todo!() todo!()
} }
fn remove_record(&mut self, name: &str, record_type: crate::topology::DnsRecordType) -> Result<(), crate::topology::DnsError> { fn remove_record(&mut self, _name: &str, _record_type: crate::topology::DnsRecordType) -> Result<(), crate::topology::DnsError> {
todo!() todo!()
} }
fn list_records(&self) -> Vec<crate::topology::DnsRecord> { fn list_records(&self) -> Vec<crate::topology::DnsRecord> {
todo!() todo!()
} }
fn get_ip(&self) -> IpAddress {
self.ip_address.clone()
}
} }

View File

@ -3,18 +3,18 @@ use derive_new::new;
use log::info; use log::info;
use crate::{domain::{ use crate::{domain::{
data::{Id, Version}, executors::SshClient, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress data::{Id, Version}, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, topology::IpAddress
}, infra::executors::russh::RusshClient}; }, executors::SshClient, infra::executors::russh::RusshClient, inventory::Inventory, topology::HAClusterTopology};
use crate::domain::{ use crate::domain::{
interpret::Interpret, interpret::InterpretName, inventory::InventoryFilter, interpret::Interpret, interpret::InterpretName, inventory::InventorySlice, score::Score,
inventory::InventorySlice, score::Score,
}; };
use crate::domain::executors::{ExecutorError, ExecutorResult}; use crate::domain::executors::{ExecutorError, ExecutorResult};
#[derive(Debug, new)]
#[derive(Debug, new, Clone)]
pub struct OPNSenseDhcpScore { pub struct OPNSenseDhcpScore {
inventory_filter: InventoryFilter, topology: HAClusterTopology,
} }
impl Score for OPNSenseDhcpScore { impl Score for OPNSenseDhcpScore {
@ -30,7 +30,7 @@ impl Score for OPNSenseDhcpScore {
} }
/// https://docs.opnsense.org/manual/dhcp.html#advanced-settings /// https://docs.opnsense.org/manual/dhcp.html#advanced-settings
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct OPNSenseDhcpInterpret { pub struct OPNSenseDhcpInterpret {
score: OPNSenseDhcpScore, score: OPNSenseDhcpScore,
version: Version, version: Version,
@ -73,8 +73,8 @@ impl Interpret for OPNSenseDhcpInterpret {
todo!() todo!()
} }
async fn execute(&self, _inventory: &Inventory) -> Result<Outcome, InterpretError> { async fn execute(&self, inventory: &Inventory, _topology: &HAClusterTopology) -> Result<Outcome, InterpretError> {
info!("Executing {} on inventory {_inventory:?}", self.get_name()); info!("Executing {} on inventory {inventory:?}", self.get_name());
let ssh_client = RusshClient{}; let ssh_client = RusshClient{};
info!("RusshClient initiated"); info!("RusshClient initiated");