feat: Refactored harmony into a workspace with independent modules per client, completed RusshClient test_connection implementation. Needs refactoring though

This commit is contained in:
jeangab 2024-09-06 16:10:06 -04:00
parent cc01ec5fe5
commit 8592a3bc36
14 changed files with 116 additions and 46 deletions

6
harmony-rs/Cargo.lock generated
View File

@ -619,6 +619,12 @@ dependencies = [
[[package]] [[package]]
name = "fqm" name = "fqm"
version = "0.1.0" version = "0.1.0"
dependencies = [
"env_logger",
"harmony",
"log",
"tokio",
]
[[package]] [[package]]
name = "funty" name = "funty"

View File

@ -15,3 +15,4 @@ log = "0.4.22"
env_logger = "0.11.5" env_logger = "0.11.5"
derive-new = "0.7.0" derive-new = "0.7.0"
async-trait = "0.1.82" async-trait = "0.1.82"
tokio = { version = "1.40.0", features = ["io-std"] }

View File

@ -4,3 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
harmony = { path = "../harmony" }
log = { workspace = true }
env_logger = { workspace = true }
tokio = { workspace = true }

View File

@ -1,9 +1,11 @@
use crate::domain::{ use harmony::domain::{
hardware::{Location, Host, HostCategory}, hardware::{Host, HostCategory, Location, NetworkInterface},
inventory::Inventory, inventory::Inventory,
}; };
pub fn get_fqm_inventory() -> Inventory { pub fn get_inventory() -> Inventory {
let network = vec![NetworkInterface::new(1_000_000_000, "TODO MAC ADDRESS".into(), true ) ];
let storage = vec![];
Inventory { Inventory {
location: Location::new( location: Location::new(
"1134 Grande Allée Ouest 1er étage, Québec, Qc".into(), "1134 Grande Allée Ouest 1er étage, Québec, Qc".into(),
@ -11,8 +13,8 @@ pub fn get_fqm_inventory() -> Inventory {
), ),
host: vec![Host { host: vec![Host {
category: HostCategory::Server, category: HostCategory::Server,
network: vec![], network,
storage: vec![], storage,
labels: vec![], labels: vec![],
}], }],
switch: vec![], switch: vec![],

View File

@ -1,3 +1,4 @@
pub mod inventory;
pub fn add(left: usize, right: usize) -> usize { pub fn add(left: usize, right: usize) -> usize {
left + right left + right
} }

View File

@ -0,0 +1,22 @@
use fqm::inventory::get_inventory;
use harmony::{
domain::{
inventory::{Inventory, InventoryFilter},
maestro::Maestro,
},
modules::opnsense_dhcp::OPNSenseDhcpScore,
};
use log::info;
#[tokio::main]
async fn main() {
env_logger::init();
tokio::spawn(async move {
info!("FQM Harmony Starting");
let maestro = Maestro::new(get_inventory());
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![]));
let result = maestro.interpret(score).await.unwrap();
info!("{result}");
}).await;
}

View File

@ -11,7 +11,7 @@ rust-ipmi = "0.1.1"
semver = "1.0.23" semver = "1.0.23"
serde = { version = "1.0.209", features = ["derive"] } serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127" serde_json = "1.0.127"
tokio = { version = "1.40.0", features = ["io-std"] } tokio = { workspace = true }
derive-new = { workspace = true } derive-new = { workspace = true }
log = { workspace = true } log = { workspace = true }
env_logger = { workspace = true } env_logger = { workspace = true }

View File

@ -29,5 +29,5 @@ impl std::error::Error for ExecutorError {}
#[async_trait] #[async_trait]
pub trait SshClient { pub trait SshClient {
async fn test_connection(&self, username: String, password: String) -> Result<(), ExecutorError>; async fn test_connection(&self, username: &str, password: &str) -> Result<(), ExecutorError>;
} }

View File

@ -18,14 +18,15 @@ pub enum HostCategory {
Switch, Switch,
} }
#[derive(Debug)] #[derive(Debug, new)]
pub struct NetworkInterface { pub struct NetworkInterface {
speed: u64, speed: u64,
mac_address: MacAddress, mac_address: MacAddress,
plugged_in: bool, plugged_in: bool,
} }
type MacAddress = String; type MacAddress = String;
#[derive(Debug)]
#[derive(Debug, new)]
pub enum StorageConnectionType { pub enum StorageConnectionType {
Sata3g, Sata3g,
Sata6g, Sata6g,
@ -56,7 +57,10 @@ pub struct Switch {
#[derive(Debug)] #[derive(Debug)]
pub struct Firewall {} pub struct Firewall {}
#[derive(Debug)] #[derive(Debug)]
pub struct Label; pub struct Label {
name: String,
value: String,
}
pub type Address = String; pub type Address = String;
#[derive(new, Debug)] #[derive(new, Debug)]

View File

@ -1,7 +1,10 @@
use std::error::Error;
use async_trait::async_trait;
use derive_new::new;
use super::{ use super::{
data::{Id, Version}, data::{Id, Version}, executors::ExecutorError, inventory::Inventory, score::Score
inventory::Inventory,
score::Score,
}; };
pub enum InterpretName { pub enum InterpretName {
@ -16,19 +19,25 @@ impl std::fmt::Display for InterpretName {
} }
} }
#[async_trait]
pub trait Interpret { pub trait Interpret {
fn execute(&self, inventory: &Inventory) -> Result<Outcome, InterpretError>; async fn execute(&self, inventory: &Inventory) -> 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;
fn get_children(&self) -> Vec<Id>; fn get_children(&self) -> Vec<Id>;
} }
#[derive(Debug)] #[derive(Debug, new)]
pub struct Outcome { pub struct Outcome {
status: InterpretStatus, status: InterpretStatus,
message: String, message: String,
} }
impl std::fmt::Display for Outcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message))
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum InterpretStatus { pub enum InterpretStatus {
@ -39,7 +48,34 @@ pub enum InterpretStatus {
BLOCKED, BLOCKED,
} }
impl std::fmt::Display for InterpretStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self {
InterpretStatus::SUCCESS => "SUCCESS",
InterpretStatus::FAILURE => "FAILURE",
InterpretStatus::RUNNING => "RUNNING",
InterpretStatus::QUEUED => "QUEUED",
InterpretStatus::BLOCKED => "BLOCKED",
};
f.write_str(msg)
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct InterpretError { pub struct InterpretError {
msg: String, msg: String,
} }
impl std::fmt::Display for InterpretError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.msg)
}
}
impl Error for InterpretError {}
impl From<ExecutorError> for InterpretError{
fn from(value: ExecutorError) -> Self {
Self { msg: format!("InterpretError : {value}") }
}
}

View File

@ -1,7 +1,7 @@
use derive_new::new; use derive_new::new;
use log::info; use log::info;
use super::{interpret::Interpret, inventory::Inventory, score::Score}; use super::{interpret::{Interpret, InterpretError, Outcome}, inventory::Inventory, score::Score};
#[derive(new)] #[derive(new)]
pub struct Maestro { pub struct Maestro {
@ -28,11 +28,12 @@ impl Maestro {
todo!() todo!()
} }
pub fn interpret<S: Score>(&self, score: S) { pub async fn interpret<S: Score>(&self, score: S) -> Result<Outcome, InterpretError> {
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); let result = interpret.execute(&self.inventory).await;
info!("Got result {result:?}"); info!("Got result {result:?}");
result
} }
} }

View File

@ -1,6 +1,7 @@
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};
@ -9,11 +10,14 @@ pub struct RusshClient;
#[async_trait] #[async_trait]
impl SshClient for RusshClient { impl SshClient for RusshClient {
async fn test_connection(&self, _username: String, _password: String) -> Result<(), crate::domain::executors::ExecutorError> { async fn test_connection(&self, _username: &str, _password: &str) -> Result<(), crate::domain::executors::ExecutorError> {
let config = client::Config::default(); let config = client::Config::default();
let c = Client{}; let c = Client{};
let _client = client::connect(Arc::new(config), ("192.168.12.1", 22), c).await?; let mut client = client::connect(Arc::new(config), ("192.168.1.1", 22), c).await?;
Ok(()) match client.authenticate_password("nationtech", "opnsense").await? {
true => Ok(()),
false => Err(ExecutorError::AuthenticationError("ssh authentication failed".to_string())),
}
} }
} }
@ -36,6 +40,7 @@ impl client::Handler for Client {
impl From<russh::Error> for ExecutorError { impl From<russh::Error> for ExecutorError {
fn from(_value: russh::Error) -> Self { fn from(_value: russh::Error) -> Self {
todo!() // TODO handle various russh errors properly
ExecutorError::NetworkError("Russh client error".to_string())
} }
} }

View File

@ -1,19 +0,0 @@
use harmony::{
domain::{
inventory::{Inventory, InventoryFilter},
maestro::Maestro,
},
modules::opnsense_dhcp::OPNSenseDhcpScore,
};
pub fn main() {
env_logger::init();
let maestro = Maestro::new(get_inventory());
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![]));
maestro.interpret(score);
}
fn get_inventory() -> Inventory {
todo!()
}

View File

@ -1,8 +1,9 @@
use async_trait::async_trait;
use derive_new::new; use derive_new::new;
use log::info; use log::info;
use crate::{domain::{ use crate::{domain::{
data::{Id, Version}, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress data::{Id, Version}, executors::SshClient, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress
}, infra::executors::russh::RusshClient}; }, infra::executors::russh::RusshClient};
use crate::domain::{ use crate::domain::{
@ -54,6 +55,7 @@ impl OPNSenseDhcpInterpret {
} }
} }
#[async_trait]
impl Interpret for OPNSenseDhcpInterpret { impl Interpret for OPNSenseDhcpInterpret {
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::OPNSenseDHCP InterpretName::OPNSenseDHCP
@ -71,11 +73,15 @@ impl Interpret for OPNSenseDhcpInterpret {
todo!() todo!()
} }
fn execute(&self, _inventory: &Inventory) -> Result<Outcome, InterpretError> { async fn execute(&self, _inventory: &Inventory) -> 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{};
// ssh_client.test_connection("username", "password");
todo!() info!("RusshClient initiated");
ssh_client.test_connection("paul", "paul").await?;
info!("Connection test complete");
Ok(Outcome::new(InterpretStatus::SUCCESS, "Connection test successful".to_string()))
} }
} }