Add MariaDB IPL validation, topology docs in beds.toml, and developer wiki
- Add MariaDB (REL) IPL validation — master required, secondary non-fatal
- Add RelNodeConfig / RelInstanceConfig structs with master/secondary pattern
- Add rel_services section to beds.toml and test fixture
- Add detailed topology commentary to beds.toml covering standalone,
master/replica, Galera cluster, and multi-DB-per-node configurations
- Add developer wiki (wiki/) covering:
- Origin story — PHP Namaste history, production record, why Rust
- Architecture overview — full system diagram, all layers explained
- The four nodes — appServer, admin, segundo, tercero with real-world context
- IPL sequence — every step documented with rationale for ordering
- Configuration system — layering, env selection, adding new sections
- Queue topology — exchanges, routing keys, broker bindings, vhost isolation
- Template system — REC/REL, TLA convention, cache map, warehousing
- Event lineage — compound event IDs, parent/child tracking, msLogs schema
- Glossary
- Update README with wiki index and MariaDB status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
//! * `2026-04-02` - mks - refactored into load() + load_from() for testability
|
||||
|
||||
mod structs;
|
||||
pub use structs::{BedsConfig, BrokerServicesConfig, RecNodeConfig};
|
||||
pub use structs::{BedsConfig, BrokerServicesConfig, RecNodeConfig, RelNodeConfig, RelInstanceConfig};
|
||||
|
||||
use config::{Config, File, FileFormat};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct BedsConfig {
|
||||
pub journal_on: bool,
|
||||
pub broker_services: BrokerServicesConfig,
|
||||
pub rec_services: HashMap<String, RecNodeConfig>,
|
||||
pub rel_services: HashMap<String, RelNodeConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -60,3 +61,18 @@ pub struct RecNodeConfig {
|
||||
pub database: String,
|
||||
pub use_ssl: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RelNodeConfig {
|
||||
pub master: RelInstanceConfig,
|
||||
pub secondary: Option<RelInstanceConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct RelInstanceConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub user: String,
|
||||
pub pass: String,
|
||||
pub database: String,
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
pub mod amqp;
|
||||
pub mod config;
|
||||
pub mod logging;
|
||||
pub mod mariadb;
|
||||
pub mod mongo;
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -24,6 +24,7 @@
|
||||
mod amqp;
|
||||
mod config;
|
||||
mod logging;
|
||||
mod mariadb;
|
||||
mod mongo;
|
||||
|
||||
/// Executes the BEDS Initial Program Load (IPL) sequence.
|
||||
@@ -81,6 +82,18 @@ fn ipl() -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
// validate MariaDB reachability — fatal in production, non-fatal in all other envs
|
||||
// secondary instance failures are always non-fatal (handled inside validate_all)
|
||||
match mariadb::validate_all(&cfg.rel_services) {
|
||||
Ok(()) => tracing::info!("MariaDB reachable"),
|
||||
Err(e) => {
|
||||
if cfg.id.env_name == "production" {
|
||||
return Err(e);
|
||||
}
|
||||
tracing::warn!("MariaDB unreachable (non-fatal in {}): {}", cfg.id.env_name, e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
122
src/mariadb.rs
Normal file
122
src/mariadb.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
//! # mariadb.rs — MariaDB (REL) Transport Layer
|
||||
//!
|
||||
//! Manages all MariaDB interactions for the BEDS node. At IPL, validates that
|
||||
//! the master instance of each configured REL service node is reachable before
|
||||
//! the node proceeds. The secondary instance is optional — its absence or
|
||||
//! unreachability is logged but never fatal.
|
||||
//!
|
||||
//! Future phases will add connection pooling, authentication, and query
|
||||
//! dispatch via the adapter layer.
|
||||
//!
|
||||
//! ## Calling Agents
|
||||
//! - `ipl()` in main.rs — calls `validate_all()` during the IPL sequence
|
||||
//!
|
||||
//! ## Inputs
|
||||
//! - `HashMap<String, RelNodeConfig>` from the loaded BEDS configuration
|
||||
//!
|
||||
//! ## Outputs
|
||||
//! - `Ok(())` if all configured REL master nodes are reachable
|
||||
//! - `Err(String)` with node name, host:port, and OS error on first master failure
|
||||
//!
|
||||
//! **Author:** mks
|
||||
//! **Version:** 1.0
|
||||
//!
|
||||
//! ## History
|
||||
//! * `2026-04-04` - mks - original coding
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::{RelInstanceConfig, RelNodeConfig};
|
||||
|
||||
/// Validates that all configured MariaDB master nodes are reachable.
|
||||
///
|
||||
/// Iterates every entry in the `rel_services` config block and validates the
|
||||
/// master instance. Secondary instances are checked but their failure is
|
||||
/// non-fatal — a missing or unreachable secondary is logged as a warning.
|
||||
/// Fails on the first unreachable master.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `nodes` — map of service name → `RelNodeConfig` from `BedsConfig`
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Ok(())` if every master node responds to a TCP connect within the timeout.
|
||||
/// `Err(String)` with the service name and address of the first master failure.
|
||||
///
|
||||
/// # History
|
||||
///
|
||||
/// * `2026-04-04` - mks - original coding
|
||||
pub fn validate_all(nodes: &HashMap<String, RelNodeConfig>) -> Result<(), String> {
|
||||
for (name, node) in nodes {
|
||||
// master is required — failure is propagated to the caller
|
||||
validate(&format!("{}.master", name), &node.master)?;
|
||||
|
||||
// secondary is optional — log absence but do not fail
|
||||
if let Some(secondary) = &node.secondary {
|
||||
if let Err(e) = validate(&format!("{}.secondary", name), secondary) {
|
||||
tracing::warn!("MariaDB secondary unreachable (non-fatal): {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates that a single MariaDB instance is reachable.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `label` — descriptive label for error messages (e.g. "app_server.master")
|
||||
/// * `instance` — `RelInstanceConfig` for this instance
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Ok(())` if the TCP handshake succeeds within 5 seconds.
|
||||
/// `Err(String)` with a descriptive message on failure.
|
||||
///
|
||||
/// # History
|
||||
///
|
||||
/// * `2026-04-04` - mks - original coding
|
||||
pub fn validate(label: &str, instance: &RelInstanceConfig) -> Result<(), String> {
|
||||
let addr_str = format!("{}:{}", instance.host, instance.port);
|
||||
|
||||
let addr: std::net::SocketAddr = addr_str
|
||||
.parse()
|
||||
.map_err(|e| format!("Invalid MariaDB address for rel_services.{} ({}): {}", label, addr_str, e))?;
|
||||
|
||||
TcpStream::connect_timeout(&addr, Duration::from_secs(5))
|
||||
.map_err(|e| format!("MariaDB unreachable at rel_services.{} ({}): {}", label, addr_str, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::load_from;
|
||||
|
||||
fn test_cfg() -> crate::config::BedsConfig {
|
||||
load_from("tests/fixtures/beds_test.toml", "")
|
||||
.expect("test fixture beds_test.toml failed to load")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_err_on_closed_port() {
|
||||
let cfg = test_cfg();
|
||||
let node = cfg.rel_services.get("app_server").unwrap();
|
||||
let mut bad = node.master.clone();
|
||||
bad.port = 1;
|
||||
assert!(validate("app_server.master", &bad).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_err_on_bad_address() {
|
||||
let cfg = test_cfg();
|
||||
let node = cfg.rel_services.get("app_server").unwrap();
|
||||
let mut bad = node.master.clone();
|
||||
bad.host = "not_a_valid_host!!!".to_string();
|
||||
assert!(validate("app_server.master", &bad).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user