- Flat src/amqp.rs, src/mongo.rs, src/mariadb.rs promoted to src/services/{amqp,mongo,mariadb}/
- services/amqp/connection.rs: AmqpConnection struct with connect() and declare_exchange()
- services/amqp/error.rs: AmqpError type (thiserror, wraps lapin::Error)
- ipl() made async; #[tokio::main] added to main()
- IPL step 3b: authenticate to RabbitMQ + declare beds.events topic exchange (durable)
- Added lapin = "2" and tokio = { version = "1", features = ["full"] } to Cargo.toml
- 12 unit tests pass
- Docs: README, CLAUDE.md, wiki/04-ipl.md, wiki/06-queue-topology.md updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
3.9 KiB
Rust
119 lines
3.9 KiB
Rust
//! # services/mariadb/mod.rs — MariaDB (REL) Transport Module
|
|
//!
|
|
//! MariaDB transport layer. Provides IPL reachability validation for all
|
|
//! configured REL service nodes. Master failure is fatal; secondary failure
|
|
//! is always non-fatal. Future phases will add the MariaDbConnection struct
|
|
//! for authenticated sessions and query dispatch.
|
|
//!
|
|
//! ## 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 (flat mariadb.rs)
|
|
//! * `2026-04-04` - mks - promoted to services/mariadb/
|
|
|
|
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 {
|
|
validate(&format!("{}.master", name), &node.master)?;
|
|
|
|
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());
|
|
}
|
|
}
|