Merge remote-tracking branch 'origin/master' into feat/postgresqlScore
All checks were successful
Run Check Script / check (pull_request) Successful in 55s
All checks were successful
Run Check Script / check (pull_request) Successful in 55s
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -1963,6 +1963,25 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-opnsense-node-exporter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cidr",
|
||||
"env_logger",
|
||||
"harmony",
|
||||
"harmony_cli",
|
||||
"harmony_macros",
|
||||
"harmony_secret",
|
||||
"harmony_secret_derive",
|
||||
"harmony_types",
|
||||
"log",
|
||||
"serde",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-pxe"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Architecture Decision Record: Global Orchestration Mesh & The Harmony Agent
|
||||
|
||||
**Status:** Proposed
|
||||
**Date:** 2025-12-19
|
||||
|
||||
## Context
|
||||
|
||||
Harmony is designed to enable a truly decentralized infrastructure where independent clusters—owned by different organizations or running on diverse hardware—can collaborate reliably. This vision combines the decentralization of Web3 with the performance and capabilities of Web2.
|
||||
|
||||
Currently, Harmony operates as a stateless CLI tool, invoked manually or via CI runners. While effective for deployment, this model presents a critical limitation: **a CLI cannot react to real-time events.**
|
||||
|
||||
To achieve automated failover and dynamic workload management, we need a system that is "always on." Relying on manual intervention or scheduled CI jobs to recover from a cluster failure creates unacceptable latency and prevents us from scaling to thousands of nodes.
|
||||
|
||||
Furthermore, we face a challenge in serving diverse workloads:
|
||||
* **Financial workloads** require absolute consistency (CP - Consistency/Partition Tolerance).
|
||||
* **AI/Inference workloads** require maximum availability (AP - Availability/Partition Tolerance).
|
||||
|
||||
There are many more use cases, but those are the two extremes.
|
||||
|
||||
We need a unified architecture that automates cluster coordination and supports both consistency models without requiring a complete re-architecture in the future.
|
||||
|
||||
## Decision
|
||||
|
||||
We propose a fundamental architectural evolution. It has been clear since the start of Harmony that it would be necessary to transition Harmony from a purely ephemeral CLI tool to a system that includes a persistent **Harmony Agent**. This Agent will connect to a **Global Orchestration Mesh** based on a strongly consistent protocol.
|
||||
|
||||
The proposal consists of four key pillars:
|
||||
|
||||
### 1. The Harmony Agent (New Component)
|
||||
We will develop a long-running process (Daemon/Agent) to be deployed alongside workloads.
|
||||
* **Shift from CLI:** Unlike the CLI, which applies configuration and exits, the Agent maintains a persistent connection to the mesh.
|
||||
* **Responsibility:** It actively monitors cluster health, participates in consensus, and executes lifecycle commands (start/stop/fence) instantly when the mesh dictates a state change.
|
||||
|
||||
### 2. The Technology: NATS JetStream
|
||||
We will utilize **NATS JetStream** as the underlying transport and consensus layer for the Agent and the Mesh.
|
||||
* **Why not raw Raft?** Implementing a raw Raft library requires building and maintaining the transport layer, log compaction, snapshotting, and peer discovery manually. NATS JetStream provides a battle-tested, distributed log and Key-Value store (based on Raft) out of the box, along with a high-performance pub/sub system for event propagation.
|
||||
* **Role:** It will act as the "source of truth" for the cluster state.
|
||||
|
||||
### 3. Strong Consistency at the Mesh Layer
|
||||
The mesh will operate with **Strong Consistency** by default.
|
||||
* All critical cluster state changes (topology updates, lease acquisitions, leadership elections) will require consensus among the Agents.
|
||||
* This ensures that in the event of a network partition, we have a mathematical guarantee of which side holds the valid state, preventing data corruption.
|
||||
|
||||
### 4. Public UX: The `FailoverStrategy` Abstraction
|
||||
To keep the user experience stable and simple, we will expose the complexity of the mesh through a high-level configuration API, tentatively called `FailoverStrategy`.
|
||||
|
||||
The user defines the *intent* in their config, and the Harmony Agent automates the *execution*:
|
||||
|
||||
* **`FailoverStrategy::AbsoluteConsistency`**:
|
||||
* *Use Case:* Banking, Transactional DBs.
|
||||
* *Behavior:* If the mesh detects a partition, the Agent on the minority side immediately halts workloads. No split-brain is ever allowed.
|
||||
* **`FailoverStrategy::SplitBrainAllowed`**:
|
||||
* *Use Case:* LLM Inference, Stateless Web Servers.
|
||||
* *Behavior:* If a partition occurs, the Agent keeps workloads running to maximize uptime. State is reconciled when connectivity returns.
|
||||
|
||||
## Rationale
|
||||
|
||||
**The Necessity of an Agent**
|
||||
You cannot automate what you do not monitor. Moving to an Agent-based model is the only way to achieve sub-second reaction times to infrastructure failures. It transforms Harmony from a deployment tool into a self-healing platform.
|
||||
|
||||
**Scaling & Decentralization**
|
||||
To allow independent clusters to collaborate, they need a shared language. A strongly consistent mesh allows Cluster A (Organization X) and Cluster B (Organization Y) to agree on workload placement without a central authority.
|
||||
|
||||
**Why Strong Consistency First?**
|
||||
It is technically feasible to relax a strongly consistent system to allow for "Split Brain" behavior (AP) when the user requests it. However, it is nearly impossible to take an eventually consistent system and force it to be strongly consistent (CP) later. By starting with strict constraints, we cover the hardest use cases (Finance) immediately.
|
||||
|
||||
**Future Topologies**
|
||||
While our immediate need is `FailoverTopology` (Multi-site), this architecture supports any future topology logic:
|
||||
* **`CostTopology`**: Agents negotiate to route workloads to the cluster with the cheapest spot instances.
|
||||
* **`HorizontalTopology`**: Spreading a single workload across 100 clusters for massive scale.
|
||||
* **`GeoTopology`**: Ensuring data stays within specific legal jurisdictions.
|
||||
|
||||
The mesh provides the *capability* (consensus and messaging); the topology provides the *logic*.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive**
|
||||
* **Automation:** Eliminates manual failover, enabling massive scale.
|
||||
* **Reliability:** Guarantees data safety for critical workloads by default.
|
||||
* **Flexibility:** A single codebase serves both high-frequency trading and AI inference.
|
||||
* **Stability:** The public API remains abstract, allowing us to optimize the mesh internals without breaking user code.
|
||||
|
||||
**Negative**
|
||||
* **Deployment Complexity:** Users must now deploy and maintain a running service (the Agent) rather than just downloading a binary.
|
||||
* **Engineering Complexity:** Integrating NATS JetStream and handling distributed state machines is significantly more complex than the current CLI logic.
|
||||
|
||||
## Implementation Plan (Short Term)
|
||||
1. **Agent Bootstrap:** Create the initial scaffold for the Harmony Agent (daemon).
|
||||
2. **Mesh Integration:** Prototype NATS JetStream embedding within the Agent.
|
||||
3. **Strategy Implementation:** Add `FailoverStrategy` to the configuration schema and implement the logic in the Agent to read and act on it.
|
||||
4. **Migration:** Transition the current manual failover scripts into event-driven logic handled by the Agent.
|
||||
@@ -106,6 +106,7 @@ async fn main() {
|
||||
name: "wk2".to_string(),
|
||||
},
|
||||
],
|
||||
node_exporter: opnsense.clone(),
|
||||
switch_client: switch_client.clone(),
|
||||
network_manager: OnceLock::new(),
|
||||
};
|
||||
|
||||
@@ -83,6 +83,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
||||
name: "bootstrap".to_string(),
|
||||
},
|
||||
workers: vec![],
|
||||
node_exporter: opnsense.clone(),
|
||||
switch_client: switch_client.clone(),
|
||||
network_manager: OnceLock::new(),
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
||||
name: "cp0".to_string(),
|
||||
},
|
||||
workers: vec![],
|
||||
node_exporter: opnsense.clone(),
|
||||
switch_client: switch_client.clone(),
|
||||
network_manager: OnceLock::new(),
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ async fn main() {
|
||||
name: "cp0".to_string(),
|
||||
},
|
||||
workers: vec![],
|
||||
node_exporter: opnsense.clone(),
|
||||
switch_client: switch_client.clone(),
|
||||
network_manager: OnceLock::new(),
|
||||
};
|
||||
|
||||
21
examples/opnsense_node_exporter/Cargo.toml
Normal file
21
examples/opnsense_node_exporter/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "example-opnsense-node-exporter"
|
||||
edition = "2024"
|
||||
version.workspace = true
|
||||
readme.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
harmony = { path = "../../harmony" }
|
||||
harmony_cli = { path = "../../harmony_cli" }
|
||||
harmony_types = { path = "../../harmony_types" }
|
||||
harmony_secret = { path = "../../harmony_secret" }
|
||||
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
||||
cidr = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
harmony_macros = { path = "../../harmony_macros" }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
url = { workspace = true }
|
||||
serde.workspace = true
|
||||
async-trait.workspace = true
|
||||
80
examples/opnsense_node_exporter/src/main.rs
Normal file
80
examples/opnsense_node_exporter/src/main.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cidr::Ipv4Cidr;
|
||||
use harmony::{
|
||||
executors::ExecutorError,
|
||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||
infra::opnsense::OPNSenseManagementInterface,
|
||||
inventory::Inventory,
|
||||
modules::opnsense::node_exporter::NodeExporterScore,
|
||||
topology::{
|
||||
HAClusterTopology, LogicalHost, PreparationError, PreparationOutcome, Topology,
|
||||
UnmanagedRouter, node_exporter::NodeExporter,
|
||||
},
|
||||
};
|
||||
use harmony_macros::{ip, ipv4, mac_address};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OpnSenseTopology {
|
||||
node_exporter: Arc<dyn NodeExporter>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Topology for OpnSenseTopology {
|
||||
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
|
||||
Ok(PreparationOutcome::Success {
|
||||
details: "Success".to_string(),
|
||||
})
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
"OpnsenseTopology"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NodeExporter for OpnSenseTopology {
|
||||
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.ensure_initialized().await
|
||||
}
|
||||
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.commit_config().await
|
||||
}
|
||||
|
||||
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.reload_restart().await
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let firewall = harmony::topology::LogicalHost {
|
||||
ip: ip!("192.168.1.1"),
|
||||
name: String::from("fw0"),
|
||||
};
|
||||
|
||||
let opnsense = Arc::new(
|
||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||
);
|
||||
|
||||
let topology = OpnSenseTopology {
|
||||
node_exporter: opnsense.clone(),
|
||||
};
|
||||
|
||||
let inventory = Inventory::empty();
|
||||
|
||||
let node_exporter_score = NodeExporterScore {};
|
||||
|
||||
harmony_cli::run(
|
||||
inventory,
|
||||
topology,
|
||||
vec![Box::new(node_exporter_score)],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use harmony_types::{
|
||||
use log::debug;
|
||||
use log::info;
|
||||
|
||||
use crate::{data::FileContent, executors::ExecutorError};
|
||||
use crate::{data::FileContent, executors::ExecutorError, topology::node_exporter::NodeExporter};
|
||||
use crate::{infra::network_manager::OpenShiftNmStateNetworkManager, topology::PortConfig};
|
||||
use crate::{modules::inventory::HarmonyDiscoveryStrategy, topology::PxeOptions};
|
||||
|
||||
@@ -19,7 +19,6 @@ use super::{
|
||||
NetworkManager, PreparationError, PreparationOutcome, Router, Switch, SwitchClient,
|
||||
SwitchError, TftpServer, Topology, k8s::K8sClient,
|
||||
};
|
||||
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -32,6 +31,7 @@ pub struct HAClusterTopology {
|
||||
pub tftp_server: Arc<dyn TftpServer>,
|
||||
pub http_server: Arc<dyn HttpServer>,
|
||||
pub dns_server: Arc<dyn DnsServer>,
|
||||
pub node_exporter: Arc<dyn NodeExporter>,
|
||||
pub switch_client: Arc<dyn SwitchClient>,
|
||||
pub bootstrap_host: LogicalHost,
|
||||
pub control_plane: Vec<LogicalHost>,
|
||||
@@ -116,6 +116,7 @@ impl HAClusterTopology {
|
||||
tftp_server: dummy_infra.clone(),
|
||||
http_server: dummy_infra.clone(),
|
||||
dns_server: dummy_infra.clone(),
|
||||
node_exporter: dummy_infra.clone(),
|
||||
switch_client: dummy_infra.clone(),
|
||||
bootstrap_host: dummy_host,
|
||||
control_plane: vec![],
|
||||
@@ -320,6 +321,23 @@ impl NetworkManager for HAClusterTopology {
|
||||
async fn configure_bond(&self, config: &HostNetworkConfig) -> Result<(), NetworkError> {
|
||||
self.network_manager().await.configure_bond(config).await
|
||||
}
|
||||
|
||||
//TODO add snmp here
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NodeExporter for HAClusterTopology {
|
||||
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.ensure_initialized().await
|
||||
}
|
||||
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.commit_config().await
|
||||
}
|
||||
|
||||
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||
self.node_exporter.reload_restart().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -509,6 +527,21 @@ impl DnsServer for DummyInfra {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl NodeExporter for DummyInfra {
|
||||
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||
}
|
||||
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||
}
|
||||
|
||||
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SwitchClient for DummyInfra {
|
||||
async fn setup(&self) -> Result<(), SwitchError> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod failover;
|
||||
mod ha_cluster;
|
||||
pub mod ingress;
|
||||
pub mod node_exporter;
|
||||
pub use failover::*;
|
||||
use harmony_types::net::IpAddress;
|
||||
mod host_binding;
|
||||
|
||||
17
harmony/src/domain/topology/node_exporter.rs
Normal file
17
harmony/src/domain/topology/node_exporter.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::executors::ExecutorError;
|
||||
|
||||
#[async_trait]
|
||||
pub trait NodeExporter: Send + Sync + std::fmt::Debug {
|
||||
async fn ensure_initialized(&self) -> Result<(), ExecutorError>;
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError>;
|
||||
async fn reload_restart(&self) -> Result<(), ExecutorError>;
|
||||
}
|
||||
|
||||
// //TODO complete this impl
|
||||
// impl std::fmt::Debug for dyn NodeExporter {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// f.write_fmt(format_args!("NodeExporter ",))
|
||||
// }
|
||||
// }
|
||||
@@ -4,6 +4,7 @@ mod firewall;
|
||||
mod http;
|
||||
mod load_balancer;
|
||||
mod management;
|
||||
pub mod node_exporter;
|
||||
mod tftp;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
47
harmony/src/infra/opnsense/node_exporter.rs
Normal file
47
harmony/src/infra/opnsense/node_exporter.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use async_trait::async_trait;
|
||||
use log::debug;
|
||||
|
||||
use crate::{
|
||||
executors::ExecutorError, infra::opnsense::OPNSenseFirewall,
|
||||
topology::node_exporter::NodeExporter,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl NodeExporter for OPNSenseFirewall {
|
||||
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||
let mut config = self.opnsense_config.write().await;
|
||||
let node_exporter = config.node_exporter();
|
||||
if let Some(config) = node_exporter.get_full_config() {
|
||||
debug!(
|
||||
"Node exporter available in opnsense config, assuming it is already installed. {config:?}"
|
||||
);
|
||||
} else {
|
||||
config
|
||||
.install_package("os-node_exporter")
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ExecutorError::UnexpectedError(format!("Executor failed when trying to install os-node_exporter package with error {e:?}"
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
config
|
||||
.node_exporter()
|
||||
.enable(true)
|
||||
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
OPNSenseFirewall::commit_config(self).await
|
||||
}
|
||||
|
||||
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||
self.opnsense_config
|
||||
.write()
|
||||
.await
|
||||
.node_exporter()
|
||||
.reload_restart()
|
||||
.await
|
||||
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod node_exporter;
|
||||
mod shell;
|
||||
mod upgrade;
|
||||
pub use shell::*;
|
||||
|
||||
70
harmony/src/modules/opnsense/node_exporter.rs
Normal file
70
harmony/src/modules/opnsense/node_exporter.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use async_trait::async_trait;
|
||||
use harmony_types::id::Id;
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
data::Version,
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::{Topology, node_exporter::NodeExporter},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct NodeExporterScore {}
|
||||
|
||||
impl<T: Topology + NodeExporter> Score<T> for NodeExporterScore {
|
||||
fn name(&self) -> String {
|
||||
"NodeExporterScore".to_string()
|
||||
}
|
||||
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
Box::new(NodeExporterInterpret {})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeExporterInterpret {}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology + NodeExporter> Interpret<T> for NodeExporterInterpret {
|
||||
async fn execute(
|
||||
&self,
|
||||
_inventory: &Inventory,
|
||||
node_exporter: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
info!(
|
||||
"Making sure node exporter is initiailized: {:?}",
|
||||
node_exporter.ensure_initialized().await?
|
||||
);
|
||||
|
||||
info!("Applying Node Exporter configuration");
|
||||
|
||||
node_exporter.commit_config().await?;
|
||||
|
||||
info!("Reloading and restarting Node Exporter");
|
||||
|
||||
node_exporter.reload_restart().await?;
|
||||
|
||||
Ok(Outcome::success(format!(
|
||||
"NodeExporter successfully configured"
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_name(&self) -> InterpretName {
|
||||
InterpretName::Custom("NodeExporter")
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Version {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_status(&self) -> InterpretStatus {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_children(&self) -> Vec<Id> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ pub struct OPNsense {
|
||||
pub interfaces: NamedList<Interface>,
|
||||
pub dhcpd: NamedList<DhcpInterface>,
|
||||
pub snmpd: Snmpd,
|
||||
pub syslog: Syslog,
|
||||
pub syslog: Option<Syslog>,
|
||||
pub nat: Nat,
|
||||
pub filter: Filters,
|
||||
pub load_balancer: Option<LoadBalancer>,
|
||||
@@ -191,7 +191,7 @@ pub struct System {
|
||||
pub webgui: WebGui,
|
||||
pub usevirtualterminal: u8,
|
||||
pub disablenatreflection: Option<String>,
|
||||
pub disableconsolemenu: u8,
|
||||
pub disableconsolemenu: Option<u8>,
|
||||
pub disablevlanhwfilter: u8,
|
||||
pub disablechecksumoffloading: u8,
|
||||
pub disablesegmentationoffloading: u8,
|
||||
@@ -235,16 +235,16 @@ pub struct System {
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Ssh {
|
||||
pub group: String,
|
||||
pub noauto: u8,
|
||||
pub interfaces: MaybeString,
|
||||
pub kex: MaybeString,
|
||||
pub ciphers: MaybeString,
|
||||
pub macs: MaybeString,
|
||||
pub keys: MaybeString,
|
||||
pub enabled: String,
|
||||
pub passwordauth: u8,
|
||||
pub keysig: MaybeString,
|
||||
pub permitrootlogin: u8,
|
||||
pub noauto: Option<u8>,
|
||||
pub interfaces: Option<MaybeString>,
|
||||
pub kex: Option<MaybeString>,
|
||||
pub ciphers: Option<MaybeString>,
|
||||
pub macs: Option<MaybeString>,
|
||||
pub keys: Option<MaybeString>,
|
||||
pub enabled: Option<String>,
|
||||
pub passwordauth: Option<u8>,
|
||||
pub keysig: Option<MaybeString>,
|
||||
pub permitrootlogin: Option<u8>,
|
||||
pub rekeylimit: Option<MaybeString>,
|
||||
}
|
||||
|
||||
@@ -308,11 +308,11 @@ pub struct WebGui {
|
||||
pub protocol: String,
|
||||
#[yaserde(rename = "ssl-certref")]
|
||||
pub ssl_certref: String,
|
||||
pub port: MaybeString,
|
||||
pub port: Option<MaybeString>,
|
||||
#[yaserde(rename = "ssl-ciphers")]
|
||||
pub ssl_ciphers: MaybeString,
|
||||
pub interfaces: MaybeString,
|
||||
pub compression: MaybeString,
|
||||
pub ssl_ciphers: Option<MaybeString>,
|
||||
pub interfaces: Option<MaybeString>,
|
||||
pub compression: Option<MaybeString>,
|
||||
pub nohttpreferercheck: Option<u8>,
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ pub struct OPNsenseXmlSection {
|
||||
#[yaserde(rename = "Interfaces")]
|
||||
pub interfaces: Option<ConfigInterfaces>,
|
||||
#[yaserde(rename = "NodeExporter")]
|
||||
pub node_exporter: Option<RawXml>,
|
||||
pub node_exporter: Option<NodeExporter>,
|
||||
#[yaserde(rename = "Kea")]
|
||||
pub kea: Option<RawXml>,
|
||||
pub monit: Option<Monit>,
|
||||
@@ -1613,3 +1613,21 @@ pub struct Ifgroups {
|
||||
#[yaserde(attribute = true)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct NodeExporter {
|
||||
pub enabled: u8,
|
||||
pub listenaddress: Option<MaybeString>,
|
||||
pub listenport: u16,
|
||||
pub cpu: u8,
|
||||
pub exec: u8,
|
||||
pub filesystem: u8,
|
||||
pub loadavg: u8,
|
||||
pub meminfo: u8,
|
||||
pub netdev: u8,
|
||||
pub time: u8,
|
||||
pub devstat: u8,
|
||||
pub interrupts: u8,
|
||||
pub ntp: u8,
|
||||
pub zfs: u8,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ use crate::{
|
||||
error::Error,
|
||||
modules::{
|
||||
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::UnboundDnsConfig,
|
||||
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig,
|
||||
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig,
|
||||
node_exporter::NodeExporterConfig, tftp::TftpConfig,
|
||||
},
|
||||
};
|
||||
use log::{debug, info, trace, warn};
|
||||
@@ -13,6 +14,7 @@ use opnsense_config_xml::OPNsense;
|
||||
use russh::client;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
use super::{ConfigManager, OPNsenseShell};
|
||||
|
||||
@@ -71,6 +73,10 @@ impl Config {
|
||||
LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone())
|
||||
}
|
||||
|
||||
pub fn node_exporter(&mut self) -> NodeExporterConfig<'_> {
|
||||
NodeExporterConfig::new(&mut self.opnsense, self.shell.clone())
|
||||
}
|
||||
|
||||
pub async fn upload_files(&self, source: &str, destination: &str) -> Result<String, Error> {
|
||||
self.shell.upload_folder(source, destination).await
|
||||
}
|
||||
@@ -150,7 +156,8 @@ impl Config {
|
||||
|
||||
async fn reload_config(&mut self) -> Result<(), Error> {
|
||||
info!("Reloading opnsense live config");
|
||||
let (opnsense, sha2) = Self::get_opnsense_instance(self.repository.clone()).await?;
|
||||
let (opnsense, _sha2) = Self::get_opnsense_instance(self.repository.clone()).await?;
|
||||
self.opnsense = opnsense;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ pub mod dhcp_legacy;
|
||||
pub mod dns;
|
||||
pub mod dnsmasq;
|
||||
pub mod load_balancer;
|
||||
pub mod node_exporter;
|
||||
pub mod tftp;
|
||||
|
||||
54
opnsense-config/src/modules/node_exporter.rs
Normal file
54
opnsense-config/src/modules/node_exporter.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use opnsense_config_xml::{NodeExporter, OPNsense};
|
||||
|
||||
use crate::{config::OPNsenseShell, Error};
|
||||
|
||||
pub struct NodeExporterConfig<'a> {
|
||||
opnsense: &'a mut OPNsense,
|
||||
opnsense_shell: Arc<dyn OPNsenseShell>,
|
||||
}
|
||||
|
||||
impl<'a> NodeExporterConfig<'a> {
|
||||
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
|
||||
Self {
|
||||
opnsense,
|
||||
opnsense_shell,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_full_config(&self) -> &Option<NodeExporter> {
|
||||
&self.opnsense.opnsense.node_exporter
|
||||
}
|
||||
|
||||
fn with_node_exporter<F, R>(&mut self, f: F) -> Result<R, &'static str>
|
||||
where
|
||||
F: FnOnce(&mut NodeExporter) -> R,
|
||||
{
|
||||
match &mut self.opnsense.opnsense.node_exporter.as_mut() {
|
||||
Some(node_exporter) => Ok(f(node_exporter)),
|
||||
None => Err("node exporter is not yet installed"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, enabled: bool) -> Result<(), &'static str> {
|
||||
self.with_node_exporter(|node_exporter| node_exporter.enabled = enabled as u8)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub async fn reload_restart(&self) -> Result<(), Error> {
|
||||
self.opnsense_shell
|
||||
.exec("configctl node_exporter stop")
|
||||
.await?;
|
||||
self.opnsense_shell
|
||||
.exec("configctl template reload OPNsense/NodeExporter")
|
||||
.await?;
|
||||
self.opnsense_shell
|
||||
.exec("configctl node_exporter configtest")
|
||||
.await?;
|
||||
self.opnsense_shell
|
||||
.exec("configctl node_exporter start")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user