obnam_benchmark/
server.rs1use 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}