shoal_core/server/
conf.rs

1//! The config for a Shoal database
2
3use std::{path::PathBuf, str::FromStr};
4
5use config::{Config, ConfigError};
6use glommio::{
7    io::{DmaFile, DmaStreamReaderBuilder, DmaStreamWriter, DmaStreamWriterBuilder, OpenOptions},
8    CpuSet,
9};
10use serde::{Deserialize, Serialize};
11use tracing::level_filters::LevelFilter;
12use uuid::Uuid;
13
14use super::ServerError;
15
16/// The compute settings to use
17#[derive(Serialize, Deserialize, Default, Clone)]
18pub struct Compute {
19    /// Configure the number of cores to use, default to all
20    cores: Option<usize>,
21    /// The max amount of memory to use
22    memory: Option<usize>,
23}
24
25impl Compute {
26    /// Get the cpuset to run shoal on
27    pub fn cpus(&self) -> Result<CpuSet, ServerError> {
28        // get all online cpus
29        let online = CpuSet::online()?;
30        // if the user specified a limted number of cores then force that
31        if let Some(cores) = self.cores {
32            // limit our cores to just number specfied
33            let cores = online.into_iter().take(cores).collect::<CpuSet>();
34            Ok(cores)
35        } else {
36            Ok(online)
37        }
38    }
39}
40
41/// Help serde default interface we should bind to
42fn default_interface() -> String {
43    "127.0.0.1".to_owned()
44}
45
46/// Help serde default interface we should bind to
47fn default_port() -> usize {
48    12000
49}
50
51/// The networking settings for Shoal
52#[derive(Serialize, Deserialize, Clone)]
53pub struct Networking {
54    /// The interface to bind too
55    #[serde(default = "default_interface")]
56    pub interface: String,
57    /// The port to bind too
58    #[serde(default = "default_port")]
59    pub port: usize,
60}
61
62impl Default for Networking {
63    /// Builds a default networking struct
64    fn default() -> Self {
65        Networking {
66            interface: default_interface(),
67            port: default_port(),
68        }
69    }
70}
71
72impl Networking {
73    /// Build the address to bind too
74    pub fn to_addr(&self) -> String {
75        println!("listening on {}:{}", self.interface, self.port);
76        format!("{}:{}", self.interface, self.port)
77    }
78}
79
80/// Help serde set a default file system storage path
81fn default_fs_intent_path() -> PathBuf {
82    PathBuf::from_str("/opt/shoal/intents")
83        .expect("Failed to build default filesystem storage path")
84}
85
86/// Help serde set a default file system storage path
87fn default_fs_archives_path() -> PathBuf {
88    PathBuf::from_str("/opt/shoal/archives")
89        .expect("Failed to build default filesystem storage path")
90}
91
92/// Help serde set a default file system storage path
93fn default_fs_map_path() -> PathBuf {
94    PathBuf::from_str("/opt/shoal/maps").expect("Failed to build default filesystem storage path")
95}
96
97/// The settings for file system based storage
98#[derive(Serialize, Deserialize, Clone)]
99pub struct FileSystemStorage {
100    /// Where to store intent logs
101    #[serde(default = "default_fs_intent_path")]
102    pub intent: PathBuf,
103    /// Where to store compacted partitions
104    #[serde(default = "default_fs_archives_path")]
105    pub archives: PathBuf,
106    /// Where to store map data
107    #[serde(default = "default_fs_map_path")]
108    pub maps: PathBuf,
109}
110
111impl Default for FileSystemStorage {
112    fn default() -> Self {
113        FileSystemStorage {
114            intent: default_fs_intent_path(),
115            archives: default_fs_archives_path(),
116            maps: default_fs_map_path(),
117        }
118    }
119}
120
121impl FileSystemStorage {
122    /// Get a handle to a new empty archive stream writer
123    pub async fn get_new_archive_path(
124        &self,
125    ) -> Result<(Uuid, DmaFile, DmaStreamWriter), ServerError> {
126        // build the path to this new file
127        let mut path = self.archives.clone();
128        // get a random uuid for our file name
129        let id = Uuid::new_v4();
130        // append this random id
131        path.push(id.to_string());
132        // set the options to ensure we create a new writable file
133        let file = OpenOptions::new()
134            .create_new(true)
135            .read(true)
136            .write(true)
137            .dma_open(&path)
138            .await?;
139        // wrap our file in a stream writer
140        let writer = DmaStreamWriterBuilder::new(file.dup()?).build();
141        Ok((id, file, writer))
142    }
143
144    ///  Get a reader to an archive file
145    ///
146    /// # Arguments
147    ///
148    /// * `id` - The ID of the archive to build a path too
149    pub fn get_archive_path(&self, id: &Uuid) -> PathBuf {
150        // clone our base path
151        let mut path = self.archives.clone();
152        // build the path to this archive file
153        path.push(id.to_string());
154        path
155    }
156
157    /// Get the path to the archive map file for this shard
158    pub fn get_archive_intent_path(&self, shard_name: &str) -> PathBuf {
159        // get the base path for our map data
160        let mut path = self.maps.clone();
161        // build the path to this shards map intent log
162        path.push(format!("{}-intents", &shard_name));
163        path
164    }
165
166    /// Get the path to the temp archive map file for this shard
167    pub fn get_temp_archive_intent_path(&self, shard_name: &str) -> PathBuf {
168        // get the base path for our map data
169        let mut path = self.maps.clone();
170        // build the path to this shards map intent log
171        path.push(format!("{}-intents-temp", &shard_name));
172        path
173    }
174
175    /// Get the path to the temp archive map file for this shard
176    pub fn get_temp_archive_map_path(&self, shard_name: &str) -> PathBuf {
177        // clone our archive map base path
178        let mut path = self.maps.clone();
179        // add our shard name to this path
180        path.push(format!("{shard_name}-temp"));
181        path
182    }
183
184    /// Get the path to the archive map file
185    pub fn get_archive_map_path(&self, shard_name: &str) -> PathBuf {
186        // clone our archive map base path
187        let mut path = self.maps.clone();
188        // add our shard name to this path
189        path.push(format!("{shard_name}"));
190        path
191    }
192}
193
194/// The storage settings for Shoal
195#[derive(Serialize, Deserialize, Clone, Default)]
196pub struct Storage {
197    /// The settings for file system based storage
198    #[serde(default)]
199    pub fs: FileSystemStorage,
200}
201
202/// The different levels to log tracing info at
203#[derive(Serialize, Deserialize, Clone, Default)]
204pub enum TraceLevel {
205    /// Log everything include high verbosity low priority info
206    Trace,
207    /// Log low priority debug infomation and up
208    Debug,
209    /// Log standard priority information and up
210    #[default]
211    Info,
212    /// Log only warning and Errors
213    Warn,
214    /// Log only errors
215    Error,
216    /// Do not log anything
217    Off,
218}
219
220impl TraceLevel {
221    /// Convert this [`TraceLevels`] to a [`LevelFilter`]
222    pub fn to_filter(&self) -> LevelFilter {
223        match self {
224            TraceLevel::Trace => LevelFilter::TRACE,
225            TraceLevel::Debug => LevelFilter::DEBUG,
226            TraceLevel::Info => LevelFilter::INFO,
227            TraceLevel::Warn => LevelFilter::WARN,
228            TraceLevel::Error => LevelFilter::ERROR,
229            TraceLevel::Off => LevelFilter::OFF,
230        }
231    }
232}
233
234/// The tracing settings for Shoal
235#[derive(Serialize, Deserialize, Clone, Default)]
236pub struct Tracing {
237    // The level to log traces at
238    #[serde(default)]
239    pub level: TraceLevel,
240}
241
242/// The config for running Shoal
243#[derive(Serialize, Deserialize, Clone)]
244pub struct Conf {
245    /// The compute settings to use
246    #[serde(default)]
247    pub compute: Compute,
248    /// The networking settings to use
249    #[serde(default)]
250    pub networking: Networking,
251    /// The storage settings to use
252    #[serde(default)]
253    pub storage: Storage,
254    /// The tracing settings to use
255    #[serde(default)]
256    pub tracing: Tracing,
257}
258
259impl Conf {
260    /// Build a config from our environment and a config file
261    pub fn new(path: &str) -> Result<Self, ConfigError> {
262        // build our config sources
263        let conf = Config::builder()
264            // start with the settings in our config file
265            .add_source(config::File::with_name(path).required(false))
266            // overlay our env vars on top
267            .add_source(config::Environment::with_prefix("shoal"))
268            .build()?;
269        conf.try_deserialize()
270    }
271}