obnam_benchmark/
server.rs

1use crate::daemon::{Daemon, DaemonError, DaemonManager};
2use crate::tlsgen::{Tls, TlsError};
3use log::debug;
4use rand::random;
5use serde::Serialize;
6use std::ffi::OsStr;
7use std::ops::Range;
8use std::path::{Path, PathBuf};
9use std::time::Instant;
10use tempfile::{tempdir, TempDir};
11
12const PORT_RANGE: Range<PortNumber> = 2048..32000;
13const TIMEOUT_MS: u128 = 10_000;
14
15type PortNumber = u16;
16
17#[derive(Debug, thiserror::Error)]
18pub enum ObnamServerError {
19    #[error("took too long to pick a random port for server")]
20    Port,
21
22    #[error("error creating chunks directory: {0}")]
23    ChunksDir(std::io::Error),
24
25    #[error("failed to create temporary directory for server: {0}")]
26    TempDir(std::io::Error),
27
28    #[error("failed to write server configuration to {0}: {1}")]
29    WriteConfig(PathBuf, std::io::Error),
30
31    #[error("failed to create TLS certificate: {0}")]
32    Tls(TlsError),
33
34    #[error("failed to write TLS certificate: {0}")]
35    WriteTls(PathBuf, std::io::Error),
36
37    #[error("failed to start Obnam server: {0}")]
38    Daemon(DaemonError),
39}
40
41#[derive(Debug)]
42pub struct ObnamServer {
43    #[allow(dead_code)]
44    tempdir: TempDir,
45    chunks: PathBuf,
46    daemon: Option<Daemon>,
47    url: String,
48}
49
50impl ObnamServer {
51    pub fn new(server_binary: &Path, manager: &DaemonManager) -> Result<Self, ObnamServerError> {
52        debug!("creating ObnamServer");
53        let tempdir = tempdir().map_err(ObnamServerError::TempDir)?;
54        let config_filename = tempdir.path().join("server.yaml");
55        let chunks = tempdir.path().join("chunks");
56        let tls_key = tempdir.path().join("tls_key");
57        let tls_cert = tempdir.path().join("tls_cert");
58
59        let tls = Tls::new().map_err(ObnamServerError::Tls)?;
60        write(&tls_key, tls.key())?;
61        write(&tls_cert, tls.cert())?;
62
63        std::fs::create_dir(&chunks).map_err(ObnamServerError::ChunksDir)?;
64
65        let port = pick_port()?;
66        debug!("server listens on port {}", port);
67
68        let config = ServerConfig::new(port, chunks.clone(), tls_key, tls_cert);
69        config.write(&config_filename)?;
70
71        let daemon = manager
72            .start(
73                &[OsStr::new(server_binary), OsStr::new(&config_filename)],
74                Path::new("server.out"),
75                Path::new("server.log"),
76            )
77            .map_err(ObnamServerError::Daemon)?;
78
79        Ok(Self {
80            tempdir,
81            chunks,
82            daemon: Some(daemon),
83            url: format!("https://localhost:{}", port),
84        })
85    }
86
87    pub fn url(&self) -> String {
88        self.url.clone()
89    }
90
91    pub fn stop(&mut self) {
92        self.daemon.take();
93    }
94
95    pub fn chunks(&self) -> &Path {
96        &self.chunks
97    }
98}
99
100fn write(filename: &Path, data: &[u8]) -> Result<(), ObnamServerError> {
101    std::fs::write(filename, data)
102        .map_err(|err| ObnamServerError::WriteTls(filename.to_path_buf(), err))
103}
104
105fn pick_port() -> Result<PortNumber, ObnamServerError> {
106    let started = Instant::now();
107    while started.elapsed().as_millis() < TIMEOUT_MS {
108        let port: PortNumber = random();
109        if PORT_RANGE.contains(&port) {
110            return Ok(port);
111        }
112    }
113    Err(ObnamServerError::Port)
114}
115
116#[derive(Debug, Serialize)]
117pub struct ServerConfig {
118    address: String,
119    chunks: PathBuf,
120    tls_key: PathBuf,
121    tls_cert: PathBuf,
122}
123
124impl ServerConfig {
125    fn new(port: u16, chunks: PathBuf, tls_key: PathBuf, tls_cert: PathBuf) -> Self {
126        Self {
127            address: format!("localhost:{}", port),
128            chunks,
129            tls_key,
130            tls_cert,
131        }
132    }
133
134    fn write(&self, filename: &Path) -> Result<(), ObnamServerError> {
135        let config = serde_yaml::to_string(self).unwrap();
136        debug!("server config:\n{}\n(end config)", config);
137        std::fs::write(filename, &config)
138            .map_err(|err| ObnamServerError::WriteConfig(filename.to_path_buf(), err))?;
139        Ok(())
140    }
141}