diff --git a/control/Cargo.toml b/control/Cargo.toml index 037ba42..19175e4 100644 --- a/control/Cargo.toml +++ b/control/Cargo.toml @@ -65,7 +65,7 @@ arc-swap = "1" # Lock-free atomic pointer swaps for RCU pattern # TLS termination rustls = { version = "0.23", features = ["ring"] } # Use ring crypto provider tokio-rustls = "0.26" -rustls-pemfile = "2.1" +# rustls-pemfile removed — using rustls native PEM parsing (pki_types::pem::PemObject) [lints.clippy] # CRITICAL: Prevent lock poisoning panics and unwraps in production code @@ -89,4 +89,8 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" chrono = "0.4" +[[bench]] +name = "hot_path" +harness = false + [build-dependencies] diff --git a/control/benches/hot_path.rs b/control/benches/hot_path.rs new file mode 100644 index 0000000..3a1dd14 --- /dev/null +++ b/control/benches/hot_path.rs @@ -0,0 +1,88 @@ +//! Hot path benchmarks — measures the critical per-request operations +//! +//! Run: cargo bench -p control --bench hot_path +//! +//! Benchmarks: +//! - Circuit breaker allow_request (AtomicU64 CAS) +//! - Rate limiter try_acquire (AtomicU64 CAS) +//! - Router select_backend (route lookup + Maglev + health check via ArcSwap) + +#![allow(clippy::unwrap_used)] + +use common::{Backend, HttpMethod}; +use control::proxy::circuit_breaker::CircuitBreakerManager; +use control::proxy::rate_limiter::RateLimiter; +use control::proxy::router::Router; +use std::hint::black_box; +use std::time::{Duration, Instant}; + +fn main() { + println!("RAUTA Hot Path Benchmarks"); + println!("========================\n"); + + bench_circuit_breaker(); + bench_rate_limiter(); + bench_router_select(); +} + +fn bench_circuit_breaker() { + let manager = CircuitBreakerManager::new(5, Duration::from_secs(30)); + + // Warm up — create a breaker + manager.record_success("bench-backend"); + + let iterations = 1_000_000; + let start = Instant::now(); + for _ in 0..iterations { + black_box(manager.allow_request("bench-backend")); + } + let elapsed = start.elapsed(); + + let ns_per_op = elapsed.as_nanos() / iterations as u128; + let ops_per_sec = iterations as f64 / elapsed.as_secs_f64(); + println!( + "circuit_breaker.allow_request: {}ns/op ({:.0} ops/sec)", + ns_per_op, ops_per_sec + ); +} + +fn bench_rate_limiter() { + let limiter = RateLimiter::new(); + limiter.configure_route("/bench", 1_000_000.0, 1_000_000); + + let iterations = 1_000_000; + let start = Instant::now(); + for _ in 0..iterations { + black_box(limiter.check_rate_limit("/bench")); + } + let elapsed = start.elapsed(); + + let ns_per_op = elapsed.as_nanos() / iterations as u128; + let ops_per_sec = iterations as f64 / elapsed.as_secs_f64(); + println!( + "rate_limiter.check_rate_limit: {}ns/op ({:.0} ops/sec)", + ns_per_op, ops_per_sec + ); +} + +fn bench_router_select() { + let router = Router::new(); + let backend = Backend::new(0x7f000001, 8080, 100); + router + .add_route(HttpMethod::GET, "/api/v1/users", vec![backend]) + .unwrap(); + + let iterations = 1_000_000; + let start = Instant::now(); + for _ in 0..iterations { + black_box(router.select_backend(HttpMethod::GET, "/api/v1/users", None, None)); + } + let elapsed = start.elapsed(); + + let ns_per_op = elapsed.as_nanos() / iterations as u128; + let ops_per_sec = iterations as f64 / elapsed.as_secs_f64(); + println!( + "router.select_backend: {}ns/op ({:.0} ops/sec)", + ns_per_op, ops_per_sec + ); +} diff --git a/control/src/proxy/tls.rs b/control/src/proxy/tls.rs index a7dc1a8..031cec6 100644 --- a/control/src/proxy/tls.rs +++ b/control/src/proxy/tls.rs @@ -5,8 +5,9 @@ //! - SNI-based certificate selection //! - Integration with hyper HTTPS server +use rustls::pki_types::pem::PemObject; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls::ServerConfig; -use rustls_pemfile::{certs, private_key}; use std::io::{self, BufReader}; use std::sync::{Arc, Mutex, MutexGuard}; use tokio_rustls::TlsAcceptor; @@ -75,18 +76,19 @@ impl TlsCertificate { } /// Load certificate and private key from PEM bytes + /// + /// Uses rustls native PEM parsing (no rustls-pemfile dependency). pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result { // Validate certificate can be parsed let mut cert_reader = BufReader::new(cert_pem); - certs(&mut cert_reader) + CertificateDer::pem_reader_iter(&mut cert_reader) .collect::, _>>() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // Validate private key can be parsed let mut key_reader = BufReader::new(key_pem); - private_key(&mut key_reader) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key found"))?; + PrivateKeyDer::from_pem_reader(&mut key_reader) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; Ok(Self { cert_chain: cert_pem.to_vec(), @@ -96,17 +98,16 @@ impl TlsCertificate { /// Build rustls ServerConfig from this certificate pub fn to_server_config(&self) -> Result, io::Error> { - // Parse certificate chain + // Parse certificate chain (rustls native PEM parsing) let mut cert_reader = BufReader::new(&self.cert_chain[..]); - let certs = certs(&mut cert_reader) + let certs: Vec> = CertificateDer::pem_reader_iter(&mut cert_reader) .collect::, _>>() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // Parse private key let mut key_reader = BufReader::new(&self.private_key[..]); - let key = private_key(&mut key_reader) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key found"))?; + let key = PrivateKeyDer::from_pem_reader(&mut key_reader) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // Build server config let config = ServerConfig::builder() @@ -144,20 +145,18 @@ impl SniResolver { /// Helper to parse a TlsCertificate and return a CertifiedKey fn parse_certified_key(cert: &TlsCertificate) -> Result { - use rustls::pki_types::CertificateDer; use rustls::sign::CertifiedKey; - // Parse certificate chain + // Parse certificate chain (rustls native PEM parsing) let mut cert_reader = BufReader::new(&cert.cert_chain[..]); - let certs: Vec = certs(&mut cert_reader) + let certs: Vec> = CertificateDer::pem_reader_iter(&mut cert_reader) .collect::, _>>() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // Parse private key let mut key_reader = BufReader::new(&cert.private_key[..]); - let key = private_key(&mut key_reader) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key found"))?; + let key = PrivateKeyDer::from_pem_reader(&mut key_reader) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; // Build signing key let signing_key = rustls::crypto::ring::sign::any_supported_type(&key) @@ -482,7 +481,7 @@ mod tests { let mut root_cert_store = rustls::RootCertStore::empty(); root_cert_store.add_parsable_certificates( - rustls_pemfile::certs(&mut std::io::BufReader::new(&cert_pem[..])) + CertificateDer::pem_reader_iter(&mut std::io::BufReader::new(&cert_pem[..])) .collect::, _>>() .unwrap(), ); @@ -608,12 +607,12 @@ mod tests { let mut root_cert_store = rustls::RootCertStore::empty(); root_cert_store.add_parsable_certificates( - rustls_pemfile::certs(&mut std::io::BufReader::new(&example_cert_pem[..])) + CertificateDer::pem_reader_iter(&mut std::io::BufReader::new(&example_cert_pem[..])) .collect::, _>>() .unwrap(), ); root_cert_store.add_parsable_certificates( - rustls_pemfile::certs(&mut std::io::BufReader::new(&test_cert_pem[..])) + CertificateDer::pem_reader_iter(&mut std::io::BufReader::new(&test_cert_pem[..])) .collect::, _>>() .unwrap(), ); @@ -790,7 +789,7 @@ mod tests { let mut root_cert_store = rustls::RootCertStore::empty(); root_cert_store.add_parsable_certificates( - rustls_pemfile::certs(&mut std::io::BufReader::new(&cert_pem[..])) + CertificateDer::pem_reader_iter(&mut std::io::BufReader::new(&cert_pem[..])) .collect::, _>>() .unwrap(), ); diff --git a/eval/k8s/Dockerfile b/eval/k8s/Dockerfile new file mode 100644 index 0000000..db3ee14 --- /dev/null +++ b/eval/k8s/Dockerfile @@ -0,0 +1,18 @@ +# Oracle test image — pre-compiled test binary, no Rust toolchain needed at runtime +# +# Build: docker build -f eval/k8s/Dockerfile -t false-systems/rauta-oracle . +# Run: docker run --rm -e RAUTA_PROXY_ENDPOINT=http://host:8080 false-systems/rauta-oracle + +FROM rust:1.83-slim AS builder + +WORKDIR /build +COPY eval/oracle/ eval/oracle/ + +RUN cargo test --manifest-path eval/oracle/Cargo.toml --no-run --release 2>&1 \ + && cp $(find eval/oracle/target/release/deps -name 'rauta_oracle-*' -type f ! -name '*.d' | head -1) /oracle + +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /oracle /usr/local/bin/oracle + +ENTRYPOINT ["/usr/local/bin/oracle", "--nocapture"] diff --git a/eval/k8s/oracle-job.yaml b/eval/k8s/oracle-job.yaml new file mode 100644 index 0000000..d03916b --- /dev/null +++ b/eval/k8s/oracle-job.yaml @@ -0,0 +1,45 @@ +# Oracle K8s Job — runs the oracle test suite against a live RAUTA in-cluster +# +# Prerequisites: +# 1. RAUTA deployed in the 'rauta-system' namespace (see manifests/) +# 2. Oracle image built: docker build -f eval/k8s/Dockerfile -t false-systems/rauta-oracle . +# +# Usage: +# kubectl apply -f eval/k8s/oracle-job.yaml +# kubectl logs -f job/rauta-oracle -n rauta-system +# +# Configure endpoints via env vars if your deployment differs. + +apiVersion: batch/v1 +kind: Job +metadata: + name: rauta-oracle + namespace: rauta-system + labels: + app.kubernetes.io/name: rauta-oracle + app.kubernetes.io/component: eval +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 3600 + template: + metadata: + labels: + app.kubernetes.io/name: rauta-oracle + spec: + restartPolicy: Never + containers: + - name: oracle + image: false-systems/rauta-oracle:latest + imagePullPolicy: Always + env: + - name: RAUTA_PROXY_ENDPOINT + value: "http://rauta-metrics.rauta-system.svc:8080" + - name: RAUTA_ADMIN_ENDPOINT + value: "http://rauta-metrics.rauta-system.svc:9091" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi