diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-12-18 10:54:15 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-12-19 11:07:02 +0200 |
commit | bdf6e5306aab0a7183d36d6a45d91e307fb9a4b7 (patch) | |
tree | 241a868b1018433f6bf17f8f85fead56e96b1b54 | |
parent | c9c620bd44a7fd84e90e16ca632335fea20e94f7 (diff) | |
download | obnam-benchmark-bdf6e5306aab0a7183d36d6a45d91e307fb9a4b7.tar.gz |
feat: add an abstraction for managing an Obnam server
Sponsored-by: author
-rw-r--r-- | Cargo.lock | 15 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/daemon.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/server.rs | 122 | ||||
-rw-r--r-- | src/suite.rs | 19 | ||||
-rw-r--r-- | src/tlsgen.rs | 138 |
7 files changed, 296 insertions, 3 deletions
@@ -562,6 +562,19 @@ dependencies = [ ] [[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] name = "no-std-compat" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -605,7 +618,9 @@ dependencies = [ "glob", "lazy_static", "log", + "nix", "pretty_env_logger", + "rand", "serde", "serde_json", "serde_yaml", @@ -10,7 +10,9 @@ edition = "2018" anyhow = "1.0.51" lazy_static = "1.4.0" log = "0.4.14" +nix = "0.23.0" pretty_env_logger = "0.4.0" +rand = "0.8.4" serde = { version = "1.0.101", features = ["derive"] } serde_json = "1.0.72" serde_yaml = "0.8.21" diff --git a/src/daemon.rs b/src/daemon.rs index c6da5f5..fa8f287 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -121,6 +121,7 @@ impl DaemonManager { /// A running daemon. /// /// The daemon process is killed, when the `Daemon` struct is dropped. +#[derive(Debug)] pub struct Daemon { pid: Option<i32>, } @@ -31,6 +31,8 @@ pub mod daemon; pub mod obnam; pub mod result; +pub mod server; pub mod specification; pub mod step; pub mod suite; +pub mod tlsgen; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..9a809a5 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,122 @@ +use crate::daemon::{Daemon, DaemonError, DaemonManager}; +use crate::tlsgen::{Tls, TlsError}; +use log::debug; +use rand::random; +use serde::Serialize; +use std::ops::Range; +use std::path::{Path, PathBuf}; +use std::time::Instant; +use tempfile::{tempdir, TempDir}; + +const PORT_RANGE: Range<PortNumber> = 2048..32000; +const TIMEOUT_MS: u128 = 10_000; + +type PortNumber = u16; + +#[derive(Debug, thiserror::Error)] +pub enum ObnamServerError { + #[error("took too long to pick a random port for server")] + Port, + + #[error("failed to create temporary directory for server: {0}")] + TempDir(std::io::Error), + + #[error("failed to write server configuration to {0}: {1}")] + WriteConfig(PathBuf, std::io::Error), + + #[error("failed to create TLS certificate: {0}")] + Tls(TlsError), + + #[error("failed to write TLS certificate: {0}")] + WriteTls(PathBuf, std::io::Error), + + #[error("failed to start Obnam server: {0}")] + Daemon(DaemonError), +} + +#[derive(Debug)] +pub struct ObnamServer { + #[allow(dead_code)] + tempdir: TempDir, + chunks: PathBuf, + daemon: Option<Daemon>, +} + +impl ObnamServer { + pub fn new(manager: &DaemonManager) -> Result<Self, ObnamServerError> { + debug!("creating ObamServer"); + let tempdir = tempdir().map_err(ObnamServerError::TempDir)?; + let config_filename = tempdir.path().join("server.yaml"); + let chunks = tempdir.path().join("chunks"); + let tls_key = tempdir.path().join("tls_key"); + let tls_cert = tempdir.path().join("tls_cert"); + + let tls = Tls::new().map_err(ObnamServerError::Tls)?; + write(&tls_key, tls.key())?; + write(&tls_cert, tls.cert())?; + + let port = pick_port()?; + + let config = ServerConfig::new(port, chunks.clone(), tls_key, tls_cert); + config.write(&config_filename)?; + + let daemon = manager + .start(&["/bin/sleep", "1000"]) + .map_err(ObnamServerError::Daemon)?; + + Ok(Self { + tempdir, + chunks, + daemon: Some(daemon), + }) + } + + pub fn stop(&mut self) { + self.daemon.take(); + } + + pub fn chunks(&self) -> &Path { + &self.chunks + } +} + +fn write(filename: &Path, data: &[u8]) -> Result<(), ObnamServerError> { + std::fs::write(filename, data) + .map_err(|err| ObnamServerError::WriteTls(filename.to_path_buf(), err)) +} + +fn pick_port() -> Result<PortNumber, ObnamServerError> { + let started = Instant::now(); + while started.elapsed().as_millis() < TIMEOUT_MS { + let port: PortNumber = random(); + if PORT_RANGE.contains(&port) { + return Ok(port); + } + } + Err(ObnamServerError::Port) +} + +#[derive(Debug, Serialize)] +pub struct ServerConfig { + address: String, + chunks: PathBuf, + tls_key: PathBuf, + tls_cert: PathBuf, +} + +impl ServerConfig { + fn new(port: u16, chunks: PathBuf, tls_key: PathBuf, tls_cert: PathBuf) -> Self { + Self { + address: format!("localhost:{}", port), + chunks, + tls_key, + tls_cert, + } + } + + fn write(&self, filename: &Path) -> Result<(), ObnamServerError> { + std::fs::write(filename, serde_yaml::to_string(self).unwrap()) + .map_err(|err| ObnamServerError::WriteConfig(filename.to_path_buf(), err))?; + Ok(()) + } +} diff --git a/src/suite.rs b/src/suite.rs index 435c476..0f49994 100644 --- a/src/suite.rs +++ b/src/suite.rs @@ -1,5 +1,7 @@ +use crate::daemon::DaemonManager; use crate::obnam::{Obnam, ObnamError}; use crate::result::{Measurement, OpMeasurements, Operation}; +use crate::server::{ObnamServer, ObnamServerError}; use crate::specification::{Create, FileCount}; use crate::step::Step; use log::{debug, info}; @@ -13,6 +15,7 @@ use walkdir::WalkDir; /// This manages temporary data created for the benchmarks, and /// executes individual steps in the suite. pub struct Suite { + manager: DaemonManager, benchmark: Option<Benchmark>, } @@ -38,11 +41,18 @@ pub enum SuiteError { /// Error managing an Obnam system. #[error(transparent)] Obnam(#[from] ObnamError), + + /// Error managing an Obnam server. + #[error(transparent)] + Server(#[from] ObnamServerError), } impl Suite { pub fn new() -> Result<Self, SuiteError> { - Ok(Self { benchmark: None }) + Ok(Self { + manager: DaemonManager::new(), + benchmark: None, + }) } /// Execute one step in the benchmark suite. @@ -54,7 +64,7 @@ impl Suite { let mut om = match step { Step::Start(name) => { assert!(self.benchmark.is_none()); - let mut benchmark = Benchmark::new(name)?; + let mut benchmark = Benchmark::new(name, &self.manager)?; let om = benchmark.start()?; self.benchmark = Some(benchmark); om @@ -104,13 +114,15 @@ struct Benchmark { // Obnam, and thereby delete any temporary files. We want to do // that intentionally, so that it can be measured. obnam: Option<Obnam>, + server: Option<ObnamServer>, } impl Benchmark { - fn new(name: &str) -> Result<Self, SuiteError> { + fn new(name: &str, manager: &DaemonManager) -> Result<Self, SuiteError> { Ok(Self { name: name.to_string(), obnam: Some(Obnam::new()?), + server: Some(ObnamServer::new(manager)?), }) } @@ -132,6 +144,7 @@ impl Benchmark { info!("ending benchmark {}", self.name); self.obnam().stop_server()?; self.obnam.take().unwrap(); // This destroys the Obnam + self.server.as_mut().unwrap().stop(); Ok(OpMeasurements::new(self.name(), Operation::Stop)) } diff --git a/src/tlsgen.rs b/src/tlsgen.rs new file mode 100644 index 0000000..03b58ad --- /dev/null +++ b/src/tlsgen.rs @@ -0,0 +1,138 @@ +use std::fs::read; +use std::path::Path; +use std::process::{Command, Stdio}; +use tempfile::NamedTempFile; + +#[derive(Debug, thiserror::Error)] +pub enum TlsError { + #[error("failed to create temporary file: {0}")] + TempFile(std::io::Error), + + #[error("failed to read temporary file: {0}")] + ReadTemp(std::io::Error), + + #[error("failed to run openssl {0}: {1}")] + RunOpenSsl(String, std::io::Error), + + #[error("openssl {0} failed: {1}")] + OpenSsl(String, String), +} + +#[derive(Debug)] +pub struct Tls { + key: Vec<u8>, + cert: Vec<u8>, +} + +impl Tls { + pub fn new() -> Result<Self, TlsError> { + let (key, cert) = generate()?; + Ok(Self { key, cert }) + } + + pub fn key(&self) -> &[u8] { + &self.key + } + + pub fn cert(&self) -> &[u8] { + &self.cert + } +} + +fn generate() -> Result<(Vec<u8>, Vec<u8>), TlsError> { + let key = NamedTempFile::new().map_err(TlsError::TempFile)?; + let csr = NamedTempFile::new().map_err(TlsError::TempFile)?; + genrsa(key.path())?; + let key_data = rsa(key.path())?; + req(key.path(), csr.path())?; + let cert_data = x509(key.path(), csr.path())?; + Ok((key_data, cert_data)) +} + +fn openssl() -> Command { + let mut command = Command::new("openssl"); + command + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + command +} + +fn genrsa(filename: &Path) -> Result<(), TlsError> { + let output = openssl() + .arg("genrsa") + .arg("-out") + .arg(filename) + .arg("2048") + .output() + .map_err(|err| TlsError::RunOpenSsl("genrsa".to_string(), err))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(TlsError::OpenSsl("genrsa".to_string(), stderr)); + } + + Ok(()) +} + +fn rsa(filename: &Path) -> Result<Vec<u8>, TlsError> { + let output = openssl() + .arg("rsa") + .arg("-in") + .arg(filename) + .arg("-out") + .arg(filename) + .output() + .map_err(|err| TlsError::RunOpenSsl("rsa".to_string(), err))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(TlsError::OpenSsl("rsa".to_string(), stderr)); + } + + read(filename).map_err(TlsError::ReadTemp) +} + +fn req(key: &Path, csr: &Path) -> Result<(), TlsError> { + let output = openssl() + .arg("req") + .arg("-sha256") + .arg("-new") + .arg("-key") + .arg(key) + .arg("-out") + .arg(csr) + .arg("-subj") + .arg("/CN=localhost") + .output() + .map_err(|err| TlsError::RunOpenSsl("req".to_string(), err))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(TlsError::OpenSsl("req".to_string(), stderr)); + } + + Ok(()) +} + +fn x509(key: &Path, csr: &Path) -> Result<Vec<u8>, TlsError> { + let output = openssl() + .arg("x509") + .arg("-req") + .arg("-sha256") + .arg("-days") + .arg("1") + .arg("-in") + .arg(csr) + .arg("-signkey") + .arg(key) + .output() + .map_err(|err| TlsError::RunOpenSsl("req".to_string(), err))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(TlsError::OpenSsl("req".to_string(), stderr)); + } + + Ok(output.stdout) +} |