Core domain structure for harmony rs (#1)
Co-authored-by: jeangab <jeangabriel.gc@gmail.com> Co-authored-by: Jean-Gabriel Gill-Couture <jeangabriel.gc@gmail.com> Reviewed-on: https://git.nationtech.io/johnride/harmony/pulls/1 Co-authored-by: jeangab <jg@nationtech.io> Co-committed-by: jeangab <jg@nationtech.io>
This commit is contained in:
parent
231b1cca9f
commit
aa28ab37b8
1101
harmony-rs/Cargo.lock
generated
1101
harmony-rs/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,14 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
derive-new = "0.7.0"
|
||||||
|
env_logger = "0.11.5"
|
||||||
libredfish = "0.1.1"
|
libredfish = "0.1.1"
|
||||||
|
log = "0.4.22"
|
||||||
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
reqwest = {version = "0.11", features = ["blocking", "json"] }
|
||||||
|
russh = "0.45.0"
|
||||||
rust-ipmi = "0.1.1"
|
rust-ipmi = "0.1.1"
|
||||||
|
semver = "1.0.23"
|
||||||
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
|
serde_json = "1.0.127"
|
||||||
|
tokio = { version = "1.40.0", features = ["io-std"] }
|
||||||
|
|||||||
0
harmony-rs/src/domain/cluster/mod.rs
Normal file
0
harmony-rs/src/domain/cluster/mod.rs
Normal file
12
harmony-rs/src/domain/data/id.rs
Normal file
12
harmony-rs/src/domain/data/id.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Id {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Id {
|
||||||
|
pub fn from_string(value: String) -> Self {
|
||||||
|
Self { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
4
harmony-rs/src/domain/data/mod.rs
Normal file
4
harmony-rs/src/domain/data/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mod id;
|
||||||
|
mod version;
|
||||||
|
pub use id::*;
|
||||||
|
pub use version::*;
|
||||||
76
harmony-rs/src/domain/data/version.rs
Normal file
76
harmony-rs/src/domain/data/version.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Version {
|
||||||
|
value: semver::Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VersionError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<semver::Error> for VersionError {
|
||||||
|
fn from(value: semver::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
msg: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Version {
|
||||||
|
pub fn from(val: &str) -> Result<Self, VersionError> {
|
||||||
|
Ok(Self {
|
||||||
|
value: semver::Version::parse(val)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for Version {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
semver::Version::parse(&s)
|
||||||
|
.map(|value| Version { value })
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for Version {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
self.value.to_string().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Version {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return self.value.fmt(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_serialize_deserialize() {
|
||||||
|
let v = "10.0.1331-ababa+b123";
|
||||||
|
let version = Version {
|
||||||
|
value: semver::Version::parse(v).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = serde_json::to_string(&version).unwrap();
|
||||||
|
let version2: Version = serde_json::from_str(&s).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(version2.value.major, 10);
|
||||||
|
assert_eq!(version2.value.minor, 0);
|
||||||
|
assert_eq!(version2.value.patch, 1331);
|
||||||
|
assert_eq!(version2.value.build.to_string(), "b123");
|
||||||
|
assert_eq!(version2.value.pre.to_string(), "ababa");
|
||||||
|
assert_eq!(version2.value.to_string(), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
harmony-rs/src/domain/executors/mod.rs
Normal file
30
harmony-rs/src/domain/executors/mod.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub struct ExecutorResult {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ExecutorError {
|
||||||
|
NetworkError(String),
|
||||||
|
AuthenticationError(String),
|
||||||
|
ConfigurationError(String),
|
||||||
|
UnexpectedError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExecutorError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ExecutorError::NetworkError(msg) => write!(f, "Network error: {}", msg),
|
||||||
|
ExecutorError::AuthenticationError(msg) => write!(f, "Authentication error: {}", msg),
|
||||||
|
ExecutorError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
|
||||||
|
ExecutorError::UnexpectedError(msg) => write!(f, "Unexpected error: {}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ExecutorError {}
|
||||||
|
|
||||||
|
pub trait SshClient {
|
||||||
|
fn test_connection(&self, username: String, password: String) -> Result<(), ExecutorError>;
|
||||||
|
}
|
||||||
15
harmony-rs/src/domain/filter.rs
Normal file
15
harmony-rs/src/domain/filter.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum FilterKind {
|
||||||
|
Label,
|
||||||
|
Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FilterValue = String;
|
||||||
|
|
||||||
|
#[derive(Debug, new, Clone)]
|
||||||
|
pub struct Filter {
|
||||||
|
kind: FilterKind,
|
||||||
|
value: FilterValue,
|
||||||
|
}
|
||||||
0
harmony-rs/src/domain/hardware/building.rs
Normal file
0
harmony-rs/src/domain/hardware/building.rs
Normal file
0
harmony-rs/src/domain/hardware/host.rs
Normal file
0
harmony-rs/src/domain/hardware/host.rs
Normal file
76
harmony-rs/src/domain/hardware/mod.rs
Normal file
76
harmony-rs/src/domain/hardware/mod.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
|
||||||
|
pub type HostGroup = Vec<Host>;
|
||||||
|
pub type SwitchGroup = Vec<Switch>;
|
||||||
|
pub type FirewallGroup = Vec<Firewall>;
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Host {
|
||||||
|
pub category: HostCategory,
|
||||||
|
pub network: Vec<NetworkInterface>,
|
||||||
|
pub storage: Vec<Storage>,
|
||||||
|
pub labels: Vec<Label>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HostCategory {
|
||||||
|
Server,
|
||||||
|
Firewall,
|
||||||
|
Switch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NetworkInterface {
|
||||||
|
speed: u64,
|
||||||
|
mac_address: MacAddress,
|
||||||
|
plugged_in: bool,
|
||||||
|
}
|
||||||
|
type MacAddress = String;
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum StorageConnectionType {
|
||||||
|
Sata3g,
|
||||||
|
Sata6g,
|
||||||
|
Sas6g,
|
||||||
|
Sas12g,
|
||||||
|
PCIE,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum StorageKind {
|
||||||
|
SSD,
|
||||||
|
NVME,
|
||||||
|
HDD,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Storage {
|
||||||
|
connection: StorageConnectionType,
|
||||||
|
kind: StorageKind,
|
||||||
|
size: u64,
|
||||||
|
serial: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Switch {
|
||||||
|
interface: Vec<NetworkInterface>,
|
||||||
|
management_interface: NetworkInterface,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Firewall {}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Label;
|
||||||
|
pub type Address = String;
|
||||||
|
|
||||||
|
#[derive(new, Debug)]
|
||||||
|
pub struct Location {
|
||||||
|
pub address: Address,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn test_building() -> Location {
|
||||||
|
Self {
|
||||||
|
address: String::new(),
|
||||||
|
name: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
harmony-rs/src/domain/hardware/network_cable.rs
Normal file
0
harmony-rs/src/domain/hardware/network_cable.rs
Normal file
0
harmony-rs/src/domain/hardware/power_cable.rs
Normal file
0
harmony-rs/src/domain/hardware/power_cable.rs
Normal file
0
harmony-rs/src/domain/hardware/rack.rs
Normal file
0
harmony-rs/src/domain/hardware/rack.rs
Normal file
0
harmony-rs/src/domain/hardware/switch.rs
Normal file
0
harmony-rs/src/domain/hardware/switch.rs
Normal file
0
harmony-rs/src/domain/hardware/ups.rs
Normal file
0
harmony-rs/src/domain/hardware/ups.rs
Normal file
45
harmony-rs/src/domain/interpret/mod.rs
Normal file
45
harmony-rs/src/domain/interpret/mod.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use super::{
|
||||||
|
data::{Id, Version},
|
||||||
|
inventory::Inventory,
|
||||||
|
score::Score,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum InterpretName {
|
||||||
|
OPNSenseDHCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for InterpretName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
InterpretName::OPNSenseDHCP => f.write_str("OPNSenseDHCP"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Interpret {
|
||||||
|
fn execute(&self, inventory: &Inventory) -> Result<Outcome, InterpretError>;
|
||||||
|
fn get_name(&self) -> InterpretName;
|
||||||
|
fn get_version(&self) -> Version;
|
||||||
|
fn get_status(&self) -> InterpretStatus;
|
||||||
|
fn get_children(&self) -> Vec<Id>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Outcome {
|
||||||
|
status: InterpretStatus,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InterpretStatus {
|
||||||
|
SUCCESS,
|
||||||
|
FAILURE,
|
||||||
|
RUNNING,
|
||||||
|
QUEUED,
|
||||||
|
BLOCKED,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterpretError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
40
harmony-rs/src/domain/inventory/mod.rs
Normal file
40
harmony-rs/src/domain/inventory/mod.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#[derive(Debug, new, Clone)]
|
||||||
|
pub struct InventoryFilter {
|
||||||
|
target: Vec<Filter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InventorySlice;
|
||||||
|
|
||||||
|
impl InventoryFilter {
|
||||||
|
pub fn apply(&self, _inventory: &Inventory) -> InventorySlice {
|
||||||
|
// TODO apply inventory filter, refactor as a slice
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use derive_new::new;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
filter::Filter,
|
||||||
|
hardware::{Location, FirewallGroup, HostGroup, SwitchGroup},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Inventory {
|
||||||
|
pub location: Location,
|
||||||
|
pub host: HostGroup,
|
||||||
|
pub switch: SwitchGroup,
|
||||||
|
pub firewall: FirewallGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inventory {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn empty_inventory() -> Self {
|
||||||
|
Self {
|
||||||
|
location: Location::test_building(),
|
||||||
|
host: HostGroup::new(),
|
||||||
|
switch: SwitchGroup::new(),
|
||||||
|
firewall: FirewallGroup::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
harmony-rs/src/domain/maestro/mod.rs
Normal file
38
harmony-rs/src/domain/maestro/mod.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use super::{interpret::Interpret, inventory::Inventory, score::Score};
|
||||||
|
|
||||||
|
#[derive(new)]
|
||||||
|
pub struct Maestro {
|
||||||
|
inventory: Inventory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Maestro {
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
info!("Starting Maestro");
|
||||||
|
self.load_score();
|
||||||
|
self.load_inventory();
|
||||||
|
self.launch_interprets();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_score(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_inventory(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch_interprets(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret<S: Score>(&self, score: S) {
|
||||||
|
info!("Running score {score:?}");
|
||||||
|
let interpret: S::InterpretType = score.create_interpret();
|
||||||
|
info!("Launching interpret {interpret:?}");
|
||||||
|
let result = interpret.execute(&self.inventory);
|
||||||
|
info!("Got result {result:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
harmony-rs/src/domain/mod.rs
Normal file
9
harmony-rs/src/domain/mod.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
pub mod data;
|
||||||
|
pub mod executors;
|
||||||
|
pub mod filter;
|
||||||
|
pub mod hardware;
|
||||||
|
pub mod interpret;
|
||||||
|
pub mod inventory;
|
||||||
|
pub mod maestro;
|
||||||
|
pub mod score;
|
||||||
|
pub mod topology;
|
||||||
7
harmony-rs/src/domain/score.rs
Normal file
7
harmony-rs/src/domain/score.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use super::{interpret::Interpret, inventory::InventorySlice};
|
||||||
|
|
||||||
|
pub trait Score: Send + Sync + std::fmt::Debug {
|
||||||
|
type InterpretType: Interpret + std::fmt::Debug;
|
||||||
|
fn get_inventory_filter(&self) -> InventorySlice;
|
||||||
|
fn create_interpret(self) -> Self::InterpretType;
|
||||||
|
}
|
||||||
13
harmony-rs/src/domain/topology/mod.rs
Normal file
13
harmony-rs/src/domain/topology/mod.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use super::hardware::HostGroup;
|
||||||
|
|
||||||
|
pub struct OKDHACluster {
|
||||||
|
firewall: HostGroup,
|
||||||
|
control_plane: HostGroup,
|
||||||
|
workers: HostGroup,
|
||||||
|
ceph_hosts: HostGroup,
|
||||||
|
switch: HostGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IpAddress(IpAddr);
|
||||||
0
harmony-rs/src/infra/executors/boot/mod.rs
Normal file
0
harmony-rs/src/infra/executors/boot/mod.rs
Normal file
2
harmony-rs/src/infra/executors/mod.rs
Normal file
2
harmony-rs/src/infra/executors/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod russh;
|
||||||
|
|
||||||
0
harmony-rs/src/infra/executors/power/mod.rs
Normal file
0
harmony-rs/src/infra/executors/power/mod.rs
Normal file
104
harmony-rs/src/infra/executors/russh/mod.rs
Normal file
104
harmony-rs/src/infra/executors/russh/mod.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use std::{net::ToSocketAddrs, path::Path, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use russh::{client, keys::{key, load_secret_key}, ChannelMsg, Disconnect};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
use crate::domain::executors::SshClient;
|
||||||
|
|
||||||
|
pub struct RusshClient;
|
||||||
|
|
||||||
|
impl SshClient for RusshClient {
|
||||||
|
fn test_connection(&self, username: String, password: String) -> Result<(), crate::domain::executors::ExecutorError> {
|
||||||
|
todo!()
|
||||||
|
//Session::connect();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Client {}
|
||||||
|
|
||||||
|
// More SSH event handlers
|
||||||
|
// can be defined in this trait
|
||||||
|
// In this example, we're only using Channel, so these aren't needed.
|
||||||
|
#[async_trait]
|
||||||
|
impl client::Handler for Client {
|
||||||
|
type Error = russh::Error;
|
||||||
|
|
||||||
|
async fn check_server_key(
|
||||||
|
&mut self,
|
||||||
|
_server_public_key: &key::PublicKey,
|
||||||
|
) -> Result<bool, Self::Error> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// This struct is a convenience wrapper
|
||||||
|
/// around a russh client
|
||||||
|
pub struct Session {
|
||||||
|
session: client::Handle<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
|
||||||
|
key_path: P,
|
||||||
|
user: impl Into<String>,
|
||||||
|
addrs: A,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let key_pair = load_secret_key(key_path, None)?;
|
||||||
|
let config = client::Config {
|
||||||
|
inactivity_timeout: Some(Duration::from_secs(5)),
|
||||||
|
..<_>::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Arc::new(config);
|
||||||
|
let sh = Client {};
|
||||||
|
|
||||||
|
let mut session = client::connect(config, addrs, sh).await?;
|
||||||
|
let auth_res = session
|
||||||
|
.authenticate_publickey(user, Arc::new(key_pair))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !auth_res {
|
||||||
|
Err("Authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { session })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call(&mut self, command: &str) -> Result<u32> {
|
||||||
|
let mut channel = self.session.channel_open_session().await?;
|
||||||
|
channel.exec(true, command).await?;
|
||||||
|
|
||||||
|
let mut code = None;
|
||||||
|
let mut stdout = tokio::io::stdout();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// There's an event available on the session channel
|
||||||
|
let Some(msg) = channel.wait().await else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
match msg {
|
||||||
|
// Write data to the terminal
|
||||||
|
ChannelMsg::Data { ref data } => {
|
||||||
|
stdout.write_all(data).await?;
|
||||||
|
stdout.flush().await?;
|
||||||
|
}
|
||||||
|
// The command has returned an exit code
|
||||||
|
ChannelMsg::ExitStatus { exit_status } => {
|
||||||
|
code = Some(exit_status);
|
||||||
|
// cannot leave the loop immediately, there might still be more data to receive
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(code.expect("program did not exit cleanly"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn close(&mut self) -> Result<()> {
|
||||||
|
self.session
|
||||||
|
.disconnect(Disconnect::ByApplication, "", "English")
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
21
harmony-rs/src/infra/inventory/fqm.rs
Normal file
21
harmony-rs/src/infra/inventory/fqm.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::domain::{
|
||||||
|
hardware::{Location, Host, HostCategory},
|
||||||
|
inventory::Inventory,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get_fqm_inventory() -> Inventory {
|
||||||
|
Inventory {
|
||||||
|
location: Location::new(
|
||||||
|
"1134 Grande Allée Ouest 1er étage, Québec, Qc".into(),
|
||||||
|
"FQM 1134 1er étage".into(),
|
||||||
|
),
|
||||||
|
host: vec![Host {
|
||||||
|
category: HostCategory::Server,
|
||||||
|
network: vec![],
|
||||||
|
storage: vec![],
|
||||||
|
labels: vec![],
|
||||||
|
}],
|
||||||
|
switch: vec![],
|
||||||
|
firewall: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
1
harmony-rs/src/infra/inventory/mod.rs
Normal file
1
harmony-rs/src/infra/inventory/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod fqm;
|
||||||
2
harmony-rs/src/infra/mod.rs
Normal file
2
harmony-rs/src/infra/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod inventory;
|
||||||
|
pub mod executors;
|
||||||
3
harmony-rs/src/lib.rs
Normal file
3
harmony-rs/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod domain;
|
||||||
|
pub mod infra;
|
||||||
|
pub mod modules;
|
||||||
@ -1,20 +1,19 @@
|
|||||||
use libredfish::{Config, Redfish};
|
use harmony_rs::{
|
||||||
use reqwest::blocking::Client;
|
domain::{
|
||||||
|
inventory::{Inventory, InventoryFilter},
|
||||||
|
maestro::Maestro,
|
||||||
|
},
|
||||||
|
infra::inventory::fqm::get_fqm_inventory, modules::opnsense_dhcp::OPNSenseDhcpScore,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let client = Client::builder().danger_accept_invalid_certs(true).build().expect("Failed to build reqwest client");
|
env_logger::init();
|
||||||
let redfish = Redfish::new(
|
|
||||||
client,
|
|
||||||
Config {
|
|
||||||
user: Some(String::from("Administrator")),
|
|
||||||
endpoint: String::from("10.10.8.104/redfish/v1"),
|
|
||||||
// password: Some(String::from("YOUR_PASSWORD")),
|
|
||||||
password: Some(String::from("wrongpass")),
|
|
||||||
port: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let response = redfish.get_power_status().expect("Failed redfish request");
|
let maestro = Maestro::new(get_inventory());
|
||||||
|
let score = OPNSenseDhcpScore::new(InventoryFilter::new(vec![]));
|
||||||
println!("Got power {:?}", response);
|
maestro.interpret(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_inventory() -> Inventory {
|
||||||
|
get_fqm_inventory()
|
||||||
}
|
}
|
||||||
|
|||||||
20
harmony-rs/src/main_redfish.rs
Normal file
20
harmony-rs/src/main_redfish.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use libredfish::{Config, Redfish};
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let client = Client::builder().danger_accept_invalid_certs(true).build().expect("Failed to build reqwest client");
|
||||||
|
let redfish = Redfish::new(
|
||||||
|
client,
|
||||||
|
Config {
|
||||||
|
user: Some(String::from("Administrator")),
|
||||||
|
endpoint: String::from("10.10.8.104/redfish/v1"),
|
||||||
|
// password: Some(String::from("YOUR_PASSWORD")),
|
||||||
|
password: Some(String::from("wrongpass")),
|
||||||
|
port: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = redfish.get_power_status().expect("Failed redfish request");
|
||||||
|
|
||||||
|
println!("Got power {:?}", response);
|
||||||
|
}
|
||||||
1
harmony-rs/src/modules/mod.rs
Normal file
1
harmony-rs/src/modules/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod opnsense_dhcp;
|
||||||
119
harmony-rs/src/modules/opnsense_dhcp.rs
Normal file
119
harmony-rs/src/modules/opnsense_dhcp.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
use derive_new::new;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::{domain::{
|
||||||
|
data::{Id, Version}, executors::SshClient, hardware::NetworkInterface, interpret::{InterpretError, InterpretStatus, Outcome}, inventory::Inventory, topology::IpAddress
|
||||||
|
}, infra::executors::russh::RusshClient};
|
||||||
|
|
||||||
|
use crate::domain::{
|
||||||
|
interpret::Interpret, interpret::InterpretName, inventory::InventoryFilter,
|
||||||
|
inventory::InventorySlice, score::Score,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::domain::executors::{ExecutorError, ExecutorResult};
|
||||||
|
#[derive(Debug, new)]
|
||||||
|
pub struct OPNSenseDhcpScore {
|
||||||
|
inventory_filter: InventoryFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Score for OPNSenseDhcpScore {
|
||||||
|
type InterpretType = OPNSenseDhcpInterpret;
|
||||||
|
|
||||||
|
fn get_inventory_filter(&self) -> InventorySlice {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_interpret(self) -> OPNSenseDhcpInterpret {
|
||||||
|
OPNSenseDhcpInterpret::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://docs.opnsense.org/manual/dhcp.html#advanced-settings
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OPNSenseDhcpInterpret {
|
||||||
|
score: OPNSenseDhcpScore,
|
||||||
|
version: Version,
|
||||||
|
id: Id,
|
||||||
|
name: String,
|
||||||
|
status: InterpretStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OPNSenseDhcpInterpret {
|
||||||
|
pub fn new(score: OPNSenseDhcpScore) -> Self {
|
||||||
|
let version = Version::from("1.0.0").expect("Version should be valid");
|
||||||
|
let name = "OPNSenseDhcpScore".to_string();
|
||||||
|
let id = Id::from_string(format!("{name}_{version}"));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
version,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
score,
|
||||||
|
status: InterpretStatus::QUEUED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpret for OPNSenseDhcpInterpret {
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::OPNSenseDHCP
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> crate::domain::data::Version {
|
||||||
|
self.version.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
self.status.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<crate::domain::data::Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, _inventory: &Inventory) -> Result<Outcome, InterpretError> {
|
||||||
|
info!("Executing {} on inventory {_inventory:?}", self.get_name());
|
||||||
|
let ssh_client = RusshClient{};
|
||||||
|
// ssh_client.test_connection("username", "password");
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OPNSenseDhcpConfigEditor {
|
||||||
|
fn add_static_host(
|
||||||
|
&self,
|
||||||
|
opnsense_host: IpAddress,
|
||||||
|
credentials: OPNSenseCredentials,
|
||||||
|
interface: NetworkInterface,
|
||||||
|
address: IpAddress,
|
||||||
|
) -> Result<ExecutorResult, ExecutorError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OPNSenseCredentials {
|
||||||
|
pub user: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opnsense_dns_score_should_do_nothing_on_empty_inventory() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opnsense_dns_score_should_set_entry_for_bootstrap_node() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opnsense_dns_score_should_set_entry_for_control_plane_members() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opnsense_dns_score_should_set_entry_for_workers() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user