use std::fmt;
use std::time::Duration;
const DEFAULT_CRAWLER_MAX_RETRIES: u32 = 3;
const DEFAULT_CRAWLER_BACKOFF_MS: u64 = 1000;
const DEFAULT_CRAWLER_COLLECTOR_CONCURRENCY: usize = 2;
const DEFAULT_CRAWLER_MONTH_CONCURRENCY: usize = 2;
const DEFAULT_BACKUP_INTERVAL_HOURS: u64 = 24;
const DEFAULT_META_RETENTION_DAYS: i64 = 30;
#[derive(Debug, Clone)]
pub struct CrawlerConfig {
pub max_retries: u32,
pub backoff_ms: u64,
pub collector_concurrency: usize,
pub month_concurrency: usize,
}
impl Default for CrawlerConfig {
fn default() -> Self {
Self {
max_retries: DEFAULT_CRAWLER_MAX_RETRIES,
backoff_ms: DEFAULT_CRAWLER_BACKOFF_MS,
collector_concurrency: DEFAULT_CRAWLER_COLLECTOR_CONCURRENCY,
month_concurrency: DEFAULT_CRAWLER_MONTH_CONCURRENCY,
}
}
}
impl CrawlerConfig {
pub fn from_env() -> Self {
Self {
max_retries: std::env::var("BGPKIT_BROKER_CRAWLER_MAX_RETRIES")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_CRAWLER_MAX_RETRIES),
backoff_ms: std::env::var("BGPKIT_BROKER_CRAWLER_BACKOFF_MS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_CRAWLER_BACKOFF_MS),
collector_concurrency: std::env::var("BGPKIT_BROKER_CRAWLER_COLLECTOR_CONCURRENCY")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_CRAWLER_COLLECTOR_CONCURRENCY),
month_concurrency: std::env::var("BGPKIT_BROKER_CRAWLER_MONTH_CONCURRENCY")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_CRAWLER_MONTH_CONCURRENCY),
}
}
}
impl fmt::Display for CrawlerConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"collector_concurrency={}, month_concurrency={}, max_retries={}, backoff_ms={}",
self.collector_concurrency, self.month_concurrency, self.max_retries, self.backoff_ms
)
}
}
#[derive(Debug, Clone)]
pub struct BackupConfig {
pub destination: Option<String>,
pub interval_hours: u64,
pub heartbeat_url: Option<String>,
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
destination: None,
interval_hours: DEFAULT_BACKUP_INTERVAL_HOURS,
heartbeat_url: None,
}
}
}
impl BackupConfig {
pub fn from_env() -> Self {
Self {
destination: std::env::var("BGPKIT_BROKER_BACKUP_TO").ok(),
interval_hours: std::env::var("BGPKIT_BROKER_BACKUP_INTERVAL_HOURS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_BACKUP_INTERVAL_HOURS),
heartbeat_url: std::env::var("BGPKIT_BROKER_BACKUP_HEARTBEAT_URL").ok(),
}
}
pub fn is_enabled(&self) -> bool {
self.destination.is_some()
}
pub fn interval(&self) -> Duration {
Duration::from_secs(self.interval_hours * 60 * 60)
}
}
#[derive(Debug, Clone, Default)]
pub struct HeartbeatConfig {
pub general_url: Option<String>,
pub backup_url: Option<String>,
}
impl HeartbeatConfig {
pub fn from_env() -> Self {
Self {
general_url: std::env::var("BGPKIT_BROKER_HEARTBEAT_URL").ok(),
backup_url: std::env::var("BGPKIT_BROKER_BACKUP_HEARTBEAT_URL").ok(),
}
}
pub fn is_any_enabled(&self) -> bool {
self.general_url.is_some() || self.backup_url.is_some()
}
}
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub meta_retention_days: i64,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
meta_retention_days: DEFAULT_META_RETENTION_DAYS,
}
}
}
impl DatabaseConfig {
pub fn from_env() -> Self {
Self {
meta_retention_days: std::env::var("BGPKIT_BROKER_META_RETENTION_DAYS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_META_RETENTION_DAYS),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct BrokerConfig {
pub crawler: CrawlerConfig,
pub backup: BackupConfig,
pub heartbeat: HeartbeatConfig,
pub database: DatabaseConfig,
}
impl BrokerConfig {
pub fn new() -> Self {
Self::default()
}
pub fn from_env() -> Self {
Self {
crawler: CrawlerConfig::from_env(),
backup: BackupConfig::from_env(),
heartbeat: HeartbeatConfig::from_env(),
database: DatabaseConfig::from_env(),
}
}
pub fn display_summary(
&self,
do_update: bool,
do_api: bool,
update_interval: u64,
host: &str,
port: u16,
) -> Vec<String> {
let mut lines = Vec::new();
lines.push("=== BGPKIT Broker Configuration ===".to_string());
if do_update {
lines.push(format!(
"Periodic updates: ENABLED (interval: {} seconds)",
update_interval
));
lines.push(format!("Crawler config: {}", self.crawler));
} else {
lines.push("Periodic updates: DISABLED".to_string());
}
if do_api {
lines.push(format!("API service: ENABLED ({}:{})", host, port));
} else {
lines.push("API service: DISABLED".to_string());
}
if let Some(ref dest) = self.backup.destination {
let is_s3 = oneio::s3_url_parse(dest).is_ok();
let s3_ok = is_s3 && oneio::s3_env_check().is_ok();
if is_s3 && !s3_ok {
lines.push(format!(
"Backup: CONFIGURED to S3 ({}) every {} hours - WARNING: S3 env vars not set",
dest, self.backup.interval_hours
));
} else if is_s3 {
lines.push(format!(
"Backup: CONFIGURED to S3 ({}) every {} hours",
dest, self.backup.interval_hours
));
} else {
lines.push(format!(
"Backup: CONFIGURED to local path ({}) every {} hours",
dest, self.backup.interval_hours
));
}
} else {
lines.push("Backup: DISABLED".to_string());
}
let general = self.heartbeat.general_url.is_some();
let backup = self.heartbeat.backup_url.is_some();
match (general, backup) {
(true, true) => {
lines.push("Heartbeats: CONFIGURED (both general and backup)".to_string())
}
(true, false) => lines.push("Heartbeats: CONFIGURED (general only)".to_string()),
(false, true) => lines.push("Heartbeats: CONFIGURED (backup only)".to_string()),
(false, false) => lines.push("Heartbeats: DISABLED".to_string()),
}
lines.push(format!(
"Database: meta_retention_days={}",
self.database.meta_retention_days
));
lines.push("=====================================".to_string());
lines
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = BrokerConfig::default();
assert_eq!(config.crawler.max_retries, 3);
assert_eq!(config.crawler.backoff_ms, 1000);
assert_eq!(config.crawler.collector_concurrency, 2);
assert_eq!(config.crawler.month_concurrency, 2);
assert_eq!(config.backup.interval_hours, 24);
assert_eq!(config.database.meta_retention_days, 30);
assert!(!config.backup.is_enabled());
}
#[test]
fn test_crawler_config_display() {
let config = CrawlerConfig::default();
let display = format!("{}", config);
assert!(display.contains("collector_concurrency=2"));
assert!(display.contains("month_concurrency=2"));
assert!(display.contains("max_retries=3"));
assert!(display.contains("backoff_ms=1000"));
}
#[test]
fn test_backup_interval() {
let config = BackupConfig {
destination: Some("test".to_string()),
interval_hours: 12,
heartbeat_url: None,
};
assert_eq!(config.interval(), Duration::from_secs(12 * 60 * 60));
}
}