//! PrismNET network management server binary use anyhow::anyhow; use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; use prismnet_api::{ port_service_server::PortServiceServer, security_group_service_server::SecurityGroupServiceServer, subnet_service_server::SubnetServiceServer, vpc_service_server::VpcServiceServer, }; use prismnet_server::{ NetworkMetadataStore, OvnClient, PortServiceImpl, SecurityGroupServiceImpl, ServerConfig, SubnetServiceImpl, VpcServiceImpl, }; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; use tonic_health::server::health_reporter; use tracing_subscriber::EnvFilter; /// PrismNET network management server #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Configuration file path #[arg(short, long, default_value = "prismnet.toml")] config: PathBuf, /// gRPC API address (overrides config) #[arg(long)] grpc_addr: Option, /// ChainFire metadata endpoint (optional, uses in-memory if not set) #[arg(long)] chainfire_endpoint: Option, /// Log level (overrides config) #[arg(short, long)] log_level: Option, /// Metrics port for Prometheus scraping #[arg(long, default_value = "9096")] metrics_port: u16, } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); // Load configuration from file or use defaults let mut config = if args.config.exists() { let contents = tokio::fs::read_to_string(&args.config).await?; toml::from_str(&contents)? } else { tracing::info!( "Config file not found: {}, using defaults", args.config.display() ); ServerConfig::default() }; // Apply command line overrides if let Some(grpc_addr_str) = args.grpc_addr { config.grpc_addr = grpc_addr_str.parse()?; } if let Some(log_level) = args.log_level { config.log_level = log_level; } if let Some(chainfire_endpoint) = args.chainfire_endpoint { config.chainfire_endpoint = Some(chainfire_endpoint); } // Initialize tracing tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new(&config.log_level)), ) .init(); tracing::info!("Starting PrismNET server"); tracing::info!(" gRPC: {}", config.grpc_addr); // Initialize Prometheus metrics exporter let metrics_addr = format!("0.0.0.0:{}", args.metrics_port); let builder = PrometheusBuilder::new(); builder .with_http_listener(metrics_addr.parse::()?) .install() .expect("Failed to install Prometheus metrics exporter"); tracing::info!( "Prometheus metrics available at http://{}/metrics", metrics_addr ); // Create metadata store let metadata = if let Some(endpoint) = &config.chainfire_endpoint { tracing::info!(" Metadata: ChainFire @ {}", endpoint); Arc::new( NetworkMetadataStore::new(Some(endpoint.clone())) .await .map_err(|e| anyhow!("Failed to init metadata store: {}", e))?, ) } else { tracing::info!(" Metadata: in-memory (no persistence)"); Arc::new(NetworkMetadataStore::new_in_memory()) }; // Initialize OVN client (default: mock) let ovn = Arc::new(OvnClient::from_env().map_err(|e| anyhow!("Failed to init OVN client: {}", e))?); // Create gRPC services let vpc_service = VpcServiceImpl::new(metadata.clone(), ovn.clone()); let subnet_service = SubnetServiceImpl::new(metadata.clone()); let port_service = PortServiceImpl::new(metadata.clone(), ovn.clone()); let sg_service = SecurityGroupServiceImpl::new(metadata.clone(), ovn.clone()); // Setup health service let (mut health_reporter, health_service) = health_reporter(); health_reporter .set_serving::>() .await; health_reporter .set_serving::>() .await; health_reporter .set_serving::>() .await; health_reporter .set_serving::>() .await; // Parse address let grpc_addr: SocketAddr = config.grpc_addr; // Configure TLS if enabled let mut server = Server::builder(); if let Some(tls_config) = &config.tls { tracing::info!("TLS enabled, loading certificates..."); let cert = tokio::fs::read(&tls_config.cert_file).await?; let key = tokio::fs::read(&tls_config.key_file).await?; let server_identity = Identity::from_pem(cert, key); let tls = if tls_config.require_client_cert { tracing::info!("mTLS enabled"); let ca_cert = tokio::fs::read( tls_config .ca_file .as_ref() .ok_or("ca_file required for mTLS")?, ) .await?; let ca = Certificate::from_pem(ca_cert); ServerTlsConfig::new() .identity(server_identity) .client_ca_root(ca) } else { ServerTlsConfig::new().identity(server_identity) }; server = server.tls_config(tls)?; } // Start gRPC server tracing::info!("gRPC server listening on {}", grpc_addr); server .add_service(health_service) .add_service(VpcServiceServer::new(vpc_service)) .add_service(SubnetServiceServer::new(subnet_service)) .add_service(PortServiceServer::new(port_service)) .add_service(SecurityGroupServiceServer::new(sg_service)) .serve(grpc_addr) .await?; Ok(()) }