Merge pull request #236 from skytable/config/modeset

Implement dev/prod mode, error stack and fix config bugs
next
Glydr 3 years ago committed by GitHub
commit 5c5fe3573f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,7 @@ All changes in this project will be noted in this file.
### Additions
- Added `dev/prod` mode for making sure that the recommended production settings are used
- Added support for system native endian storage (backward compatible)
## Version 0.7.2

206
Cargo.lock generated

@ -112,9 +112,9 @@ dependencies = [
[[package]]
name = "clipboard-win"
version = "4.3.0"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1951fb8aa063a2ee18b4b4d217e4aa2ec9cc4f2430482983f607fa10cd36d7aa"
checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
dependencies = [
"error-code",
"str-buf",
@ -181,7 +181,7 @@ dependencies = [
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"parking_lot 0.11.2",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -248,11 +248,32 @@ dependencies = [
"termcolor",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "error-code"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
@ -260,13 +281,13 @@ dependencies = [
[[package]]
name = "fd-lock"
version = "3.0.2"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
checksum = "fcef756dea9cf3db5ce73759cf0467330427a786b47711b8d6c97620d718ceb9"
dependencies = [
"cfg-if",
"libc",
"windows-sys",
"rustix",
"windows-sys 0.30.0",
]
[[package]]
@ -358,6 +379,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ef6787e7f0faedc040f95716bdd0e62bcfcf4ba93da053b62dea2691c13864"
dependencies = [
"winapi",
]
[[package]]
name = "itoa"
version = "1.0.1"
@ -393,9 +423,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.112"
version = "0.2.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
[[package]]
name = "libsky"
@ -415,11 +445,17 @@ dependencies = [
"rayon",
]
[[package]]
name = "linux-raw-sys"
version = "0.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95f5690fef754d905294c56f7ac815836f2513af966aa47f2e07ac79be07827f"
[[package]]
name = "lock_api"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
dependencies = [
"scopeguard",
]
@ -581,7 +617,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
"parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core 0.9.0",
]
[[package]]
@ -598,6 +644,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f4f894f3865f6c0e02810fc597300f34dc2510f66400da262d8ae10e75767d"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.29.0",
]
[[package]]
name = "pin-project-lite"
version = "0.2.8"
@ -633,9 +692,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
@ -751,6 +810,20 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustix"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cee647393af53c750e15dcbf7781cdd2e550b246bde76e46c326e7ea3c73773"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"winapi",
]
[[package]]
name = "rustyline"
version = "9.1.2"
@ -789,18 +862,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.133"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
@ -809,9 +882,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.75"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"ryu",
@ -901,7 +974,7 @@ dependencies = [
"log",
"num_cpus",
"openssl",
"parking_lot",
"parking_lot 0.12.0",
"rand",
"regex",
"serde",
@ -976,9 +1049,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.85"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
@ -1031,9 +1104,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.15.0"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
dependencies = [
"bytes",
"libc",
@ -1041,7 +1114,7 @@ dependencies = [
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"parking_lot 0.11.2",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
@ -1161,46 +1234,89 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceb069ac8b2117d36924190469735767f0990833935ab430155e71a44bafe148"
dependencies = [
"windows_aarch64_msvc 0.29.0",
"windows_i686_gnu 0.29.0",
"windows_i686_msvc 0.29.0",
"windows_x86_64_gnu 0.29.0",
"windows_x86_64_msvc 0.29.0",
]
[[package]]
name = "windows-sys"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
"windows_aarch64_msvc 0.30.0",
"windows_i686_gnu 0.30.0",
"windows_i686_msvc 0.30.0",
"windows_x86_64_gnu 0.30.0",
"windows_x86_64_msvc 0.30.0",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b"
[[package]]
name = "windows_aarch64_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]]
name = "windows_i686_gnu"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4"
[[package]]
name = "windows_i686_msvc"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_x86_64_gnu"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
[[package]]
name = "yaml-rust"

@ -14,7 +14,7 @@ skytable = { git = "https://github.com/skytable/client-rust", branch = "next", f
"aio-sslv",
], default-features = false }
# external deps
tokio = { version = "1.15.0", features = ["full"] }
clap = { version = "2.34.0", features = ["yaml"] }
tokio = { version = "1.16.1", features = ["full"] }
clap = { version = "2", features = ["yaml"] }
rustyline = "9.1.2"
crossterm = "0.22.1"

@ -11,6 +11,7 @@ host = "127.0.0.1" # The IP address to which you want sdb to bind to
port = 2003 # The port to which you want sdb to bind to
noart = false # Set `noart` to true if you want to disable terminal artwork
maxcon = 50000 # set the maximum number of clients that the server can accept
mode = "dev" # Set this to `prod` when you're running in production and `dev` when in development
# This key is *OPTIONAL*
[bgsave]

@ -14,16 +14,16 @@ skytable = { git = "https://github.com/skytable/client-rust", branch = "next", d
ahash = "0.7.6"
bytes = "1.1.0"
chrono = "0.4.19"
clap = { version = "2.34.0", features = ["yaml"] }
clap = { version = "2", features = ["yaml"] }
env_logger = "0.9.0"
hashbrown = { version = "0.12.0", features = ["raw"] }
log = "0.4.14"
num_cpus = "1.13.1"
openssl = { version = "0.10.38", features = ["vendored"] }
parking_lot = "0.11.2"
parking_lot = "0.12.0"
regex = "1.5.4"
serde = { version = "1.0.133", features = ["derive"] }
tokio = { version = "1.15.0", features = ["full"] }
serde = { version = "1.0.136", features = ["derive"] }
tokio = { version = "1.16.1", features = ["full"] }
tokio-openssl = "0.6.3"
toml = "0.5.8"
@ -48,10 +48,10 @@ skytable = { git = "https://github.com/skytable/client-rust", features = [
# external deps
bincode = "1.3.3"
rand = "0.8.4"
tokio = { version = "1.15.0", features = ["test-util"] }
tokio = { version = "1.16.1", features = ["test-util"] }
[target.'cfg(unix)'.dependencies]
# external deps
libc = "0.2.112"
libc = "0.2.116"
[features]
nightly = []

@ -102,6 +102,13 @@ args:
takes_value: true
help: Set the maximum number of connections
value_name: maxcon
- mode:
required: false
long: mode
takes_value: true
short: m
help: Sets the deployment type
value_name: mode
subcommands:
- upgrade:
about: Upgrades old datsets to the latest format supported by this server edition

@ -0,0 +1,123 @@
/*
* Created on Fri Jan 28 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use super::{ConfigSourceParseResult, Configset, TryFromConfigSource};
use clap::ArgMatches;
/// A flag. The flag is said to be set if `self.set` is true and unset if `self.set` is false. However,
/// if the flag is set, the value of SWITCH determines what value it is set to
#[derive(Copy, Clone)]
pub(super) struct Flag<const SWITCH: bool> {
set: bool,
}
impl<const SWITCH: bool> Flag<SWITCH> {
pub(super) fn new(set: bool) -> Self {
Self { set }
}
}
impl<const SWITCH: bool> TryFromConfigSource<bool> for Flag<SWITCH> {
fn is_present(&self) -> bool {
self.set
}
fn mutate_failed(self, target: &mut bool, trip: &mut bool) -> bool {
if self.set {
*trip = true;
*target = SWITCH;
}
false
}
fn try_parse(self) -> ConfigSourceParseResult<bool> {
if self.set {
ConfigSourceParseResult::Okay(SWITCH)
} else {
ConfigSourceParseResult::Absent
}
}
}
pub(super) fn parse_cli_args(matches: ArgMatches) -> Configset {
let mut defset = Configset::new_cli();
macro_rules! fcli {
($fn:ident, $($source:expr, $key:literal),*) => {
defset.$fn(
$(
$source,
$key,
)*
)
};
}
// server settings
fcli!(
server_tcp,
matches.value_of("host"),
"--host",
matches.value_of("port"),
"--port"
);
fcli!(
server_noart,
Flag::<true>::new(matches.is_present("noart")),
"--noart"
);
fcli!(server_mode, matches.value_of("mode"), "--mode");
fcli!(server_maxcon, matches.value_of("maxcon"), "--maxcon");
// bgsave settings
fcli!(
bgsave_settings,
Flag::<false>::new(matches.is_present("nosave")),
"--nosave",
matches.value_of("saveduration"),
"--saveduration"
);
// snapshot settings
fcli!(
snapshot_settings,
matches.value_of("snapevery"),
"--snapevery",
matches.value_of("snapkeep"),
"--snapkeep",
matches.value_of("stop-write-on-fail"),
"--stop-write-on-fail"
);
// TLS settings
fcli!(
tls_settings,
matches.value_of("sslkey"),
"--sslkey",
matches.value_of("sslchain"),
"--sslchain",
matches.value_of("sslport"),
"--sslport",
Flag::<true>::new(matches.is_present("sslonly")),
"--sslonly",
matches.value_of("tlspass"),
"--tlspassin"
);
defset
}

@ -1,5 +1,5 @@
/*
* Created on Sat Oct 02 2021
* Created on Thu Jan 27 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
@ -7,7 +7,7 @@
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -24,122 +24,48 @@
*
*/
use super::{
BGSave, ConfigurationSet, PortConfig, SnapshotConfig, SnapshotPref, SslOpts, DEFAULT_IPV4,
DEFAULT_PORT, DEFAULT_SSL_PORT,
};
use std::env::{self, VarError};
use std::net::IpAddr;
use super::Configset;
pub(super) enum EnvError {
CfgError(&'static str),
ParseError(String),
}
pub(super) fn get_env_config() -> Result<Option<ConfigurationSet>, EnvError> {
let mut defset = ConfigurationSet::default();
let mut is_set = false;
macro_rules! getenv {
($var:ident) => {{
let var = stringify!($var);
match env::var(var) {
Ok(v) => {
// set flag to true
is_set = true;
Some(v)
}
Err(e) => match e {
VarError::NotPresent => None,
VarError::NotUnicode(..) => {
return Err(EnvError::ParseError(format!(
"Bad value for {var}. The value is not unicode",
var = var
)));
}
},
}
}};
($var:ident, $ty:ty) => {{
match getenv!($var).map(|v| v.parse::<$ty>()) {
Some(Ok(v)) => Some(v),
Some(Err(e)) => {
return Err(EnvError::ParseError(format!(
"Bad value for {var}. {e}",
var = stringify!($var),
e = e
)))
}
None => None,
}
}};
}
// get system settings
let noart = getenv!(SKY_SYSTEM_NOART, bool);
let maxcon = getenv!(SKY_SYSTEM_MAXCON, usize);
set_if_exists!(noart, defset.noart);
set_if_exists!(maxcon, defset.maxcon);
// now get port config
let port = getenv!(SKY_SYSTEM_PORT, u16).unwrap_or(DEFAULT_PORT);
let host = getenv!(SKY_SYSTEM_HOST, IpAddr).unwrap_or(DEFAULT_IPV4);
let tlsport = getenv!(SKY_TLS_PORT, u16);
let tlsonly = getenv!(SKY_TLS_ONLY, bool).unwrap_or_default();
let tlscert = getenv!(SKY_TLS_CERT, String);
let tlskey = getenv!(SKY_TLS_KEY, String);
let tls_passin = getenv!(SKY_TLS_PASSIN, String);
let portcfg = match (tlscert, tlskey) {
(Some(cert), Some(key)) => {
let sslopts = SslOpts::new(key, cert, tlsport.unwrap_or(DEFAULT_SSL_PORT), tls_passin);
if tlsonly {
PortConfig::new_secure_only(host, sslopts)
} else {
PortConfig::new_multi(host, port, sslopts)
}
}
(None, None) => {
// no TLS
if tlsonly {
log::warn!("Ignoring value for SKY_TLS_ONLY because TLS was disabled");
}
if tlsport.is_some() {
log::warn!("Ignoring value for SKY_TLS_PORT because TLS was disabled");
}
PortConfig::new_insecure_only(host, port)
}
_ => {
return Err(EnvError::CfgError(
"To use TLS, pass values for both SKY_TLS_CERT and SKY_TLS_KEY",
))
}
};
defset.ports = portcfg;
// now get bgsave
let bgsave_enabled = getenv!(SKY_BGSAVE_ENABLED, bool).unwrap_or(true);
let bgsave_duration = getenv!(SKY_BGSAVE_DURATION, u64).unwrap_or(120);
let bgsave = BGSave::new(bgsave_enabled, bgsave_duration);
defset.bgsave = bgsave;
// now get snapshot config
let snapshot_enabled = getenv!(SKY_SNAPSHOT_ENABLED, bool).unwrap_or_default();
let snapshot_duration = getenv!(SKY_SNAPSHOT_DURATION, u64);
let snapshot_keep = getenv!(SKY_SNAPSHOT_KEEP, usize);
let snapshot_failsafe = getenv!(SKY_SNAPSHOT_FAILSAFE, bool).unwrap_or(true);
let snapcfg = {
if snapshot_enabled {
match (snapshot_duration, snapshot_keep) {
(Some(duration), Some(keep)) => {
SnapshotConfig::Enabled(SnapshotPref::new(duration, keep, snapshot_failsafe))
},
_ => return Err(EnvError::CfgError("To use snapshots, you must pass values for both SKY_SNAPSHOT_DURATION and SKY_SNAPSHOT_KEEP")),
}
} else {
SnapshotConfig::default()
}
};
defset.snapshot = snapcfg;
if is_set {
Ok(Some(defset))
} else {
Ok(None)
/// Returns the environment configuration
pub(super) fn parse_env_config() -> Configset {
let mut defset = Configset::new_env();
macro_rules! fenv {
(
$fn:ident,
$(
$field:ident
),*
) => {
defset.$fn(
$(
::std::env::var(stringify!($field)),
stringify!($field),
)*
);
};
}
// server settings
fenv!(server_tcp, SKY_SYSTEM_HOST, SKY_SYSTEM_PORT);
fenv!(server_noart, SKY_SYSTEM_NOART);
fenv!(server_maxcon, SKY_SYSTEM_MAXCON);
fenv!(server_mode, SKY_DEPLOY_MODE);
// bgsave settings
fenv!(bgsave_settings, SKY_BGSAVE_ENABLED, SKY_BGSAVE_DURATION);
// snapshot settings
fenv!(
snapshot_settings,
SKY_SNAPSHOT_DURATION,
SKY_SNAPSHOT_KEEP,
SKY_SNAPSHOT_FAILSAFE
);
// TLS settings
fenv!(
tls_settings,
SKY_TLS_KEY,
SKY_TLS_CERT,
SKY_TLS_PORT,
SKY_TLS_ONLY,
SKY_TLS_PASSIN
);
defset
}

@ -1,95 +0,0 @@
/*
* Created on Sat Oct 02 2021
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use super::cfgenv::EnvError;
use std::fmt;
pub(super) const ERR_CONFLICT: &str =
"Either use command line arguments, environment variables or a configuration file";
#[derive(Debug)]
/// Type of configuration error:
/// - The config file was not found (`OSError`)
/// - The config file was invalid (`SyntaxError`)
/// - The config file has an invalid value, which is syntatically correct
/// but logically incorrect (`CfgError`)
/// - The command line arguments have an invalid value/invalid values (`CliArgError`)
pub enum ConfigError {
OSError(std::io::Error),
SyntaxError(toml::de::Error),
CfgError(&'static str),
CliArgErr(&'static str),
EnvArgParseFailure(String),
}
impl PartialEq for ConfigError {
fn eq(&self, oth: &Self) -> bool {
use ConfigError::*;
match (self, oth) {
(OSError(a), OSError(b)) => a.kind() == b.kind(),
(SyntaxError(a), SyntaxError(b)) => a == b,
(CfgError(a), CfgError(b)) => a == b,
(CliArgErr(a), CliArgErr(b)) => a == b,
(EnvArgParseFailure(a), EnvArgParseFailure(b)) => a == b,
_ => false,
}
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigError::OSError(e) => write!(f, "error: {}", e),
ConfigError::SyntaxError(e) => {
write!(f, "syntax error in configuration file: {}", e)
}
ConfigError::CfgError(e) => write!(f, "Configuration error: {}", e),
ConfigError::CliArgErr(e) => write!(f, "Argument error: {}", e),
ConfigError::EnvArgParseFailure(e) => write!(f, "Environment variable error: {}", e),
}
}
}
impl From<toml::de::Error> for ConfigError {
fn from(derr: toml::de::Error) -> Self {
Self::SyntaxError(derr)
}
}
impl From<std::io::Error> for ConfigError {
fn from(derr: std::io::Error) -> Self {
Self::OSError(derr)
}
}
impl From<EnvError> for ConfigError {
fn from(err: EnvError) -> Self {
match err {
EnvError::CfgError(e) => Self::CfgError(e),
EnvError::ParseError(e) => Self::EnvArgParseFailure(e),
}
}
}

@ -24,6 +24,7 @@
*
*/
use super::{ConfigSourceParseResult, Configset, Modeset, OptString, TryFromConfigSource};
use serde::Deserialize;
use std::net::IpAddr;
@ -52,6 +53,8 @@ pub struct ConfigKeyServer {
pub(super) noart: Option<bool>,
/// The maximum number of clients
pub(super) maxclient: Option<usize>,
/// The deployment mode
pub(super) mode: Option<Modeset>,
}
/// The BGSAVE section in the config file
@ -89,3 +92,134 @@ pub struct KeySslOpts {
pub(super) only: Option<bool>,
pub(super) passin: Option<String>,
}
/// A custom non-null type for config files
pub struct NonNull<T> {
val: T,
}
impl<T> From<T> for NonNull<T> {
fn from(val: T) -> Self {
Self { val }
}
}
impl<T> TryFromConfigSource<T> for NonNull<T> {
fn is_present(&self) -> bool {
true
}
fn mutate_failed(self, target: &mut T, trip: &mut bool) -> bool {
*target = self.val;
*trip = true;
false
}
fn try_parse(self) -> ConfigSourceParseResult<T> {
ConfigSourceParseResult::Okay(self.val)
}
}
pub struct Optional<T> {
base: Option<T>,
}
impl<T> Optional<T> {
pub const fn some(val: T) -> Self {
Self { base: Some(val) }
}
}
impl<T> From<Option<T>> for Optional<T> {
fn from(base: Option<T>) -> Self {
Self { base }
}
}
impl<T> TryFromConfigSource<T> for Optional<T> {
fn is_present(&self) -> bool {
self.base.is_some()
}
fn mutate_failed(self, target: &mut T, trip: &mut bool) -> bool {
if let Some(v) = self.base {
*trip = true;
*target = v;
}
false
}
fn try_parse(self) -> ConfigSourceParseResult<T> {
match self.base {
Some(v) => ConfigSourceParseResult::Okay(v),
None => ConfigSourceParseResult::Absent,
}
}
}
type ConfigFile = Config;
pub fn from_file(file: ConfigFile) -> Configset {
let mut set = Configset::new_file();
let ConfigFile {
server,
bgsave,
snapshot,
ssl,
} = file;
// server settings
set.server_tcp(
Optional::some(server.host),
"server.host",
Optional::some(server.port),
"server.port",
);
set.server_maxcon(Optional::from(server.maxclient), "server.maxcon");
set.server_noart(Optional::from(server.noart), "server.noart");
set.server_mode(Optional::from(server.mode), "server.mode");
// bgsave settings
if let Some(bgsave) = bgsave {
let ConfigKeyBGSAVE { enabled, every } = bgsave;
set.bgsave_settings(
Optional::from(enabled),
"bgsave.enabled",
Optional::from(every),
"bgsave.every",
);
}
// snapshot settings
if let Some(snapshot) = snapshot {
let ConfigKeySnapshot {
every,
atmost,
failsafe,
} = snapshot;
set.snapshot_settings(
NonNull::from(every),
"snapshot.every",
NonNull::from(atmost),
"snapshot.atmost",
Optional::from(failsafe),
"snapshot.failsafe",
);
}
// TLS settings
if let Some(tls) = ssl {
let KeySslOpts {
key,
chain,
port,
only,
passin,
} = tls;
set.tls_settings(
NonNull::from(key),
"ssl.key",
NonNull::from(chain),
"ssl.chain",
NonNull::from(port),
"ssl.port",
Optional::from(only),
"ssl.only",
OptString::from(passin),
"ssl.passin",
);
}
set
}

@ -0,0 +1,378 @@
/*
* Created on Fri Jan 28 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use super::{feedback::WarningStack, DEFAULT_IPV4, DEFAULT_PORT};
use crate::dbnet::MAXIMUM_CONNECTION_LIMIT;
use core::fmt;
use core::str::FromStr;
use serde::{
de::{self, Deserializer, Visitor},
Deserialize,
};
use std::net::IpAddr;
/// The BGSAVE configuration
///
/// If BGSAVE is enabled, then the duration (corresponding to `every`) is wrapped in the `Enabled`
/// variant. Otherwise, the `Disabled` variant is to be used
#[derive(PartialEq, Debug)]
pub enum BGSave {
Enabled(u64),
Disabled,
}
impl BGSave {
/// Create a new BGSAVE configuration with all the fields
pub const fn new(enabled: bool, every: u64) -> Self {
if enabled {
BGSave::Enabled(every)
} else {
BGSave::Disabled
}
}
/// The default BGSAVE configuration
///
/// Defaults:
/// - `enabled`: true
/// - `every`: 120
pub const fn default() -> Self {
BGSave::new(true, 120)
}
/// Check if BGSAVE is disabled
pub const fn is_disabled(&self) -> bool {
matches!(self, Self::Disabled)
}
}
/// A `ConfigurationSet` which can be used by main::check_args_or_connect() to bind
/// to a `TcpListener` and show the corresponding terminal output for the given
/// configuration
#[derive(Debug, PartialEq)]
pub struct ConfigurationSet {
/// If `noart` is set to true, no terminal artwork should be displayed
pub noart: bool,
/// The BGSAVE configuration
pub bgsave: BGSave,
/// The snapshot configuration
pub snapshot: SnapshotConfig,
/// Port configuration
pub ports: PortConfig,
/// The maximum number of connections
pub maxcon: usize,
/// The deployment mode
pub mode: Modeset,
}
impl ConfigurationSet {
pub const fn new(
noart: bool,
bgsave: BGSave,
snapshot: SnapshotConfig,
ports: PortConfig,
maxcon: usize,
mode: Modeset,
) -> Self {
Self {
noart,
bgsave,
snapshot,
ports,
maxcon,
mode,
}
}
/// Create a default `ConfigurationSet` with the following setup defaults:
/// - `host`: 127.0.0.1
/// - `port` : 2003
/// - `noart` : false
/// - `bgsave_enabled` : true
/// - `bgsave_duration` : 120
/// - `ssl` : disabled
pub const fn default() -> Self {
Self::new(
false,
BGSave::default(),
SnapshotConfig::default(),
PortConfig::new_insecure_only(DEFAULT_IPV4, 2003),
MAXIMUM_CONNECTION_LIMIT,
Modeset::Dev,
)
}
/// Returns `false` if `noart` is enabled. Otherwise it returns `true`
pub const fn is_artful(&self) -> bool {
!self.noart
}
}
/// Port configuration
///
/// This enumeration determines whether the ports are:
/// - `Multi`: This means that the database server will be listening to both
/// SSL **and** non-SSL requests
/// - `SecureOnly` : This means that the database server will only accept SSL requests
/// and will not even activate the non-SSL socket
/// - `InsecureOnly` : This indicates that the server would only accept non-SSL connections
/// and will not even activate the SSL socket
#[derive(Debug, PartialEq)]
pub enum PortConfig {
SecureOnly {
host: IpAddr,
ssl: SslOpts,
},
Multi {
host: IpAddr,
port: u16,
ssl: SslOpts,
},
InsecureOnly {
host: IpAddr,
port: u16,
},
}
impl Default for PortConfig {
fn default() -> PortConfig {
PortConfig::InsecureOnly {
host: DEFAULT_IPV4,
port: DEFAULT_PORT,
}
}
}
impl PortConfig {
pub const fn new_secure_only(host: IpAddr, ssl: SslOpts) -> Self {
PortConfig::SecureOnly { host, ssl }
}
pub const fn new_insecure_only(host: IpAddr, port: u16) -> Self {
PortConfig::InsecureOnly { host, port }
}
pub fn get_host(&self) -> IpAddr {
match self {
Self::InsecureOnly { host, .. }
| Self::SecureOnly { host, .. }
| Self::Multi { host, .. } => *host,
}
}
pub fn upgrade_to_tls(&mut self, ssl: SslOpts) {
match self {
Self::InsecureOnly { host, port } => {
*self = Self::Multi {
host: *host,
port: *port,
ssl,
}
}
Self::SecureOnly { .. } | Self::Multi { .. } => {
panic!("Port config is already upgraded to TLS")
}
}
}
pub const fn insecure_only(&self) -> bool {
matches!(self, Self::InsecureOnly { .. })
}
}
#[derive(Deserialize, Debug, PartialEq)]
pub struct SslOpts {
pub key: String,
pub chain: String,
pub port: u16,
pub passfile: Option<String>,
}
impl SslOpts {
pub const fn new(key: String, chain: String, port: u16, passfile: Option<String>) -> Self {
SslOpts {
key,
chain,
port,
passfile,
}
}
}
#[derive(Debug, PartialEq)]
/// The snapshot configuration
///
pub struct SnapshotPref {
/// Capture a snapshot `every` seconds
pub every: u64,
/// The maximum numeber of snapshots to be kept
pub atmost: usize,
/// Lock writes if snapshotting fails
pub poison: bool,
}
impl SnapshotPref {
/// Create a new a new `SnapshotPref` instance
pub const fn new(every: u64, atmost: usize, poison: bool) -> Self {
SnapshotPref {
every,
atmost,
poison,
}
}
/// Returns `every,almost` as a tuple for pattern matching
pub const fn decompose(self) -> (u64, usize, bool) {
(self.every, self.atmost, self.poison)
}
}
#[derive(Debug, PartialEq)]
/// Snapshotting configuration
///
/// The variant `Enabled` directly carries a `ConfigKeySnapshot` object that
/// is parsed from the configuration file, The variant `Disabled` is a ZST, and doesn't
/// hold any data
pub enum SnapshotConfig {
/// Snapshotting is enabled: this variant wraps around a `SnapshotPref`
/// object
Enabled(SnapshotPref),
/// Snapshotting is disabled
Disabled,
}
impl SnapshotConfig {
/// Snapshots are disabled by default, so `SnapshotConfig::Disabled` is the
/// default configuration
pub const fn default() -> Self {
SnapshotConfig::Disabled
}
}
type RestoreFile = Option<String>;
#[derive(Debug, PartialEq)]
/// The type of configuration:
/// - The default configuration
/// - A custom supplied configuration
pub struct ConfigType {
pub(super) config: ConfigurationSet,
restore: RestoreFile,
is_custom: bool,
warnings: Option<WarningStack>,
}
impl ConfigType {
fn _new(
config: ConfigurationSet,
restore: RestoreFile,
is_custom: bool,
warnings: Option<WarningStack>,
) -> Self {
Self {
config,
restore,
is_custom,
warnings,
}
}
pub fn print_warnings(&self) {
if let Some(warnings) = self.warnings.as_ref() {
warnings.print_warnings()
}
}
pub fn finish(self) -> (ConfigurationSet, Option<String>) {
(self.config, self.restore)
}
pub fn is_custom(&self) -> bool {
self.is_custom
}
pub fn is_artful(&self) -> bool {
self.config.is_artful()
}
pub fn new_custom(
config: ConfigurationSet,
restore: RestoreFile,
warnings: WarningStack,
) -> Self {
Self::_new(config, restore, true, Some(warnings))
}
pub fn new_default(restore: RestoreFile) -> Self {
Self::_new(ConfigurationSet::default(), restore, false, None)
}
/// Check if the current deploy mode is prod
pub const fn is_prod_mode(&self) -> bool {
matches!(self.config.mode, Modeset::Prod)
}
pub fn wpush(&mut self, w: impl ToString) {
match self.warnings.as_mut() {
Some(stack) => stack.push(w),
None => {
self.warnings = {
let mut wstack = WarningStack::new("");
wstack.push(w);
Some(wstack)
};
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum Modeset {
Dev,
Prod,
}
impl FromStr for Modeset {
type Err = ();
fn from_str(st: &str) -> Result<Modeset, Self::Err> {
match st {
"dev" => Ok(Modeset::Dev),
"prod" => Ok(Modeset::Prod),
_ => Err(()),
}
}
}
struct ModesetVisitor;
impl<'de> Visitor<'de> for ModesetVisitor {
type Value = Modeset;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Expecting a string with the deployment mode")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match value {
"dev" => Ok(Modeset::Dev),
"prod" => Ok(Modeset::Prod),
_ => return Err(E::custom(format!("Bad value `{value}` for modeset"))),
}
}
}
impl<'de> Deserialize<'de> for Modeset {
fn deserialize<D>(deserializer: D) -> Result<Modeset, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(ModesetVisitor)
}
}

@ -0,0 +1,306 @@
/*
* Created on Tue Jan 25 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
// external imports
use toml::de::Error as TomlError;
// std imports
use core::fmt;
use core::ops;
use std::io::Error as IoError;
// internal imports
use super::{ConfigurationSet, SnapshotConfig, SnapshotPref};
#[cfg(unix)]
use crate::util::os::ResourceLimit;
#[cfg(test)]
const EMSG_ENV: &str = "Environment";
const EMSG_PROD: &str = "Production mode";
const TAB: &str = " ";
#[derive(Debug, PartialEq)]
pub struct FeedbackStack {
stack: Vec<String>,
feedback_type: &'static str,
feedback_source: &'static str,
}
impl FeedbackStack {
fn new(feedback_source: &'static str, feedback_type: &'static str) -> Self {
Self {
stack: Vec::new(),
feedback_type,
feedback_source,
}
}
pub fn source(&self) -> &'static str {
self.feedback_source
}
pub fn push(&mut self, f: impl ToString) {
self.stack.push(f.to_string())
}
}
impl fmt::Display for FeedbackStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_empty() {
write!(f, "{} {}:", self.feedback_source, self.feedback_type)?;
for err in self.stack.iter() {
write!(f, "\n{}- {}", TAB, err)?;
}
}
Ok(())
}
}
impl ops::Deref for FeedbackStack {
type Target = Vec<String>;
fn deref(&self) -> &Self::Target {
&self.stack
}
}
impl ops::DerefMut for FeedbackStack {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stack
}
}
#[derive(Debug, PartialEq)]
pub struct ErrorStack {
feedback: FeedbackStack,
}
impl ErrorStack {
pub fn new(err_source: &'static str) -> Self {
Self {
feedback: FeedbackStack::new(err_source, "errors"),
}
}
}
impl fmt::Display for ErrorStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.feedback)
}
}
impl ops::Deref for ErrorStack {
type Target = FeedbackStack;
fn deref(&self) -> &Self::Target {
&self.feedback
}
}
impl ops::DerefMut for ErrorStack {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.feedback
}
}
#[derive(Debug, PartialEq)]
pub struct WarningStack {
feedback: FeedbackStack,
}
impl WarningStack {
pub fn new(warning_source: &'static str) -> Self {
Self {
feedback: FeedbackStack::new(warning_source, "warnings"),
}
}
pub fn print_warnings(&self) {
if !self.feedback.is_empty() {
log::warn!("{}", self);
}
}
}
impl ops::Deref for WarningStack {
type Target = FeedbackStack;
fn deref(&self) -> &Self::Target {
&self.feedback
}
}
impl ops::DerefMut for WarningStack {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.feedback
}
}
impl fmt::Display for WarningStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.feedback)
}
}
#[derive(Debug)]
pub enum ConfigError {
OSError(IoError),
CfgError(ErrorStack),
ConfigFileParseError(TomlError),
Conflict,
ProdError(ErrorStack),
}
impl PartialEq for ConfigError {
fn eq(&self, oth: &Self) -> bool {
match (self, oth) {
(Self::OSError(lhs), Self::OSError(rhs)) => lhs.to_string() == rhs.to_string(),
(Self::CfgError(lhs), Self::CfgError(rhs)) => lhs == rhs,
(Self::ConfigFileParseError(lhs), Self::ConfigFileParseError(rhs)) => lhs == rhs,
(Self::Conflict, Self::Conflict) => true,
(Self::ProdError(lhs), Self::ProdError(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl From<IoError> for ConfigError {
fn from(e: IoError) -> Self {
Self::OSError(e)
}
}
impl From<toml::de::Error> for ConfigError {
fn from(e: toml::de::Error) -> Self {
Self::ConfigFileParseError(e)
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ConfigFileParseError(e) => write!(f, "Configuration file parse failed: {}", e),
Self::OSError(e) => write!(f, "OS Error: {}", e),
Self::CfgError(e) => write!(f, "{}", e),
Self::Conflict => write!(
f,
"Conflict error: Either provide CLI args, environment variables or a config file for configuration"
),
Self::ProdError(e) => write!(f, "You have invalid configuration for production mode. {}", e)
}
}
}
#[cfg(unix)]
fn check_rlimit_or_err(current: usize, estack: &mut ErrorStack) -> Result<(), ConfigError> {
let rlim = ResourceLimit::get()?;
if rlim.is_over_limit(current) {
estack.push(
"The value for maximum connections exceeds available resources to the server process",
);
estack.push(
format!(
"The current process is set to a resource limit of {current} and can be set to a maximum limit of {max} in the OS",
current=rlim.current(),max=rlim.max()
));
}
Ok(())
}
#[cfg(not(unix))]
fn check_rlimit_or_err(_: usize, _: &mut ErrorStack) -> Result<(), ConfigError> {
Ok(())
}
/// Check if the settings are suitable for use in production mode
pub(super) fn evaluate_prod_settings(cfg: &ConfigurationSet) -> Result<(), ConfigError> {
let mut estack = ErrorStack::new(EMSG_PROD);
// check `noart`
if cfg.is_artful() {
estack.push("Terminal artwork should be disabled");
}
// first check BGSAVE
if cfg.bgsave.is_disabled() {
estack.push("BGSAVE must be enabled");
}
// now check snapshot settings (failsafe)
if let SnapshotConfig::Enabled(SnapshotPref { poison, .. }) = cfg.snapshot {
if !poison {
estack.push("Snapshots must be failsafe");
}
}
// now check TLS settings
if cfg.ports.insecure_only() {
estack.push("Either multi-socket (TCP and TLS) or TLS only must be enabled");
}
check_rlimit_or_err(cfg.maxcon, &mut estack)?;
if estack.is_empty() {
Ok(())
} else {
Err(ConfigError::ProdError(estack))
}
}
#[cfg(test)]
mod test {
use super::{ConfigError, ErrorStack, WarningStack, EMSG_ENV, EMSG_PROD};
#[test]
fn errorstack_fmt() {
const EXPECTED: &str = "\
Environment errors:
- Invalid value for `SKY_SYSTEM_PORT`. Expected a 16-bit integer\
";
let mut estk = ErrorStack::new(EMSG_ENV);
estk.push("Invalid value for `SKY_SYSTEM_PORT`. Expected a 16-bit integer");
let fmt = format!("{}", estk);
assert_eq!(fmt, EXPECTED);
}
#[test]
fn warningstack_fmt() {
const EXPECTED: &str = "\
Environment warnings:
- BGSAVE is disabled. You may lose data if the host crashes
- The setting for `maxcon` is too high\
";
let mut wstk = WarningStack::new(EMSG_ENV);
wstk.push("BGSAVE is disabled. You may lose data if the host crashes");
wstk.push("The setting for `maxcon` is too high");
let fmt = format!("{}", wstk);
assert_eq!(fmt, EXPECTED);
}
#[test]
fn prod_mode_error_fmt() {
let mut estack = ErrorStack::new(EMSG_PROD);
estack.push("BGSAVE must be enabled");
estack.push("Snapshots must be failsafe");
estack.push("Either multi-socket (TCP and TLS) or TLS-only mode must be enabled");
estack.push(
"The value for maximum connections exceeds available resources to the server process",
);
let e = ConfigError::ProdError(estack);
const EXPECTED: &str = "\
You have invalid configuration for production mode. Production mode errors:
- BGSAVE must be enabled
- Snapshots must be failsafe
- Either multi-socket (TCP and TLS) or TLS-only mode must be enabled
- The value for maximum connections exceeds available resources to the server process\
";
assert_eq!(format!("{}", e), EXPECTED);
}
}

File diff suppressed because it is too large Load Diff

@ -24,248 +24,802 @@
*
*/
use super::cfgerr::{ConfigError, ERR_CONFLICT};
use super::{
BGSave, ConfigurationSet, IpAddr, PortConfig, SnapshotConfig, SnapshotPref, SslOpts,
DEFAULT_IPV4, DEFAULT_PORT, MAXIMUM_CONNECTION_LIMIT,
};
use clap::{load_yaml, App};
use super::{BGSave, Configset, PortConfig, SnapshotConfig, SnapshotPref, SslOpts, DEFAULT_IPV4};
pub(super) use libsky::TResult;
use std::fs;
use std::net::Ipv6Addr;
// server tests
// TCP
#[test]
fn test_config_toml_okayport() {
let file = r#"
[server]
host = "127.0.0.1"
port = 2003
"#
.to_owned();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
assert_eq!(cfg, ConfigurationSet::default(),);
}
/// Gets a `toml` file from `WORKSPACEROOT/examples/config-files`
fn get_toml_from_examples_dir(filename: String) -> TResult<String> {
use std::path;
let curdir = std::env::current_dir().unwrap();
let workspaceroot = curdir.ancestors().nth(1).unwrap();
let mut fileloc = path::PathBuf::from(workspaceroot);
fileloc.push("examples");
fileloc.push("config-files");
fileloc.push(filename);
Ok(fs::read_to_string(fileloc)?)
fn server_tcp() {
let mut cfgset = Configset::new_env();
cfgset.server_tcp(
Some("127.0.0.1"),
"SKY_SERVER_HOST",
Some("2004"),
"SKY_SERVER_PORT",
);
assert_eq!(
cfgset.cfg.ports,
PortConfig::new_insecure_only(DEFAULT_IPV4, 2004)
);
assert!(cfgset.is_mutated());
assert!(cfgset.is_okay());
}
#[test]
fn test_config_toml_badport() {
let file = r#"
[server]
port = 20033002
"#
.to_owned();
let cfg = ConfigurationSet::new_from_toml_str(file);
assert!(cfg.is_err());
fn server_tcp_fail_host() {
let mut cfgset = Configset::new_env();
cfgset.server_tcp(
Some("?127.0.0.1"),
"SKY_SERVER_HOST",
Some("2004"),
"SKY_SERVER_PORT",
);
assert_eq!(
cfgset.cfg.ports,
PortConfig::new_insecure_only(DEFAULT_IPV4, 2004)
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfgset.estack[0],
"Bad value for `SKY_SERVER_HOST`. Expected an IPv4/IPv6 address"
);
}
#[test]
fn test_config_file_ok() {
let file = get_toml_from_examples_dir("skyd.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
assert_eq!(cfg, ConfigurationSet::default());
fn server_tcp_fail_port() {
let mut cfgset = Configset::new_env();
cfgset.server_tcp(
Some("127.0.0.1"),
"SKY_SERVER_HOST",
Some("65537"),
"SKY_SERVER_PORT",
);
assert_eq!(
cfgset.cfg.ports,
PortConfig::new_insecure_only(DEFAULT_IPV4, 2003)
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfgset.estack[0],
"Bad value for `SKY_SERVER_PORT`. Expected a 16-bit positive integer"
);
}
#[test]
fn test_config_file_err() {
let file = get_toml_from_examples_dir("skyd.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_file(file);
assert!(cfg.is_err());
fn server_tcp_fail_both() {
let mut cfgset = Configset::new_env();
cfgset.server_tcp(
Some("?127.0.0.1"),
"SKY_SERVER_HOST",
Some("65537"),
"SKY_SERVER_PORT",
);
assert_eq!(
cfgset.cfg.ports,
PortConfig::new_insecure_only(DEFAULT_IPV4, 2003)
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfgset.estack[0],
"Bad value for `SKY_SERVER_HOST`. Expected an IPv4/IPv6 address"
);
assert_eq!(
cfgset.estack[1],
"Bad value for `SKY_SERVER_PORT`. Expected a 16-bit positive integer"
);
}
// noart
#[test]
fn test_args() {
let cmdlineargs = vec!["skyd", "--withconfig", "../examples/config-files/skyd.toml"];
let cfg_layout = load_yaml!("../cli.yml");
let matches = App::from_yaml(cfg_layout).get_matches_from(cmdlineargs);
let filename = matches.value_of("config").unwrap();
assert_eq!("../examples/config-files/skyd.toml", filename);
let cfg =
ConfigurationSet::new_from_toml_str(std::fs::read_to_string(filename).unwrap()).unwrap();
assert_eq!(cfg, ConfigurationSet::default());
fn server_noart_okay() {
let mut cfgset = Configset::new_env();
cfgset.server_noart(Some("true"), "SKY_SYSTEM_NOART");
assert!(!cfgset.cfg.is_artful());
assert!(cfgset.is_okay());
assert!(cfgset.is_mutated());
}
#[test]
fn test_config_file_noart() {
let file = get_toml_from_examples_dir("secure-noart.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn server_noart_fail() {
let mut cfgset = Configset::new_env();
cfgset.server_noart(Some("truee"), "SKY_SYSTEM_NOART");
assert!(cfgset.cfg.is_artful());
assert!(!cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet {
noart: true,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
cfgset.estack[0],
"Bad value for `SKY_SYSTEM_NOART`. Expected true/false"
);
assert!(cfgset.is_mutated());
}
#[test]
fn test_config_file_ipv6() {
let file = get_toml_from_examples_dir("ipv6.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
assert_eq!(
cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::new_insecure_only(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1)),
DEFAULT_PORT
),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
);
fn server_maxcon_okay() {
let mut cfgset = Configset::new_env();
cfgset.server_maxcon(Some("12345"), "SKY_SYSTEM_MAXCON");
assert!(cfgset.is_mutated());
assert!(cfgset.is_okay());
assert_eq!(cfgset.cfg.maxcon, 12345);
}
#[test]
fn test_config_file_template() {
let file = get_toml_from_examples_dir("template.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn server_maxcon_fail() {
let mut cfgset = Configset::new_env();
cfgset.server_maxcon(Some("12345A"), "SKY_SYSTEM_MAXCON");
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet::new(
false,
BGSave::default(),
SnapshotConfig::Enabled(SnapshotPref::new(3600, 4, true)),
PortConfig::new_secure_only(
DEFAULT_IPV4,
SslOpts::new(
"/path/to/keyfile.pem".into(),
"/path/to/chain.pem".into(),
2004,
Some("/path/to/cert/passphrase.txt".to_owned())
)
),
MAXIMUM_CONNECTION_LIMIT
)
cfgset.estack[0],
"Bad value for `SKY_SYSTEM_MAXCON`. Expected a positive integer greater than zero"
);
assert_eq!(cfgset.cfg.maxcon, 50000);
}
// bgsave settings
#[test]
fn test_config_file_bad_bgsave_section() {
let file = get_toml_from_examples_dir("badcfg2.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file);
assert!(cfg.is_err());
fn bgsave_okay() {
let mut cfgset = Configset::new_env();
cfgset.bgsave_settings(
Some("true"),
"SKY_BGSAVE_ENABLED",
Some("128"),
"SKY_BGSAVE_DURATION",
);
assert!(cfgset.is_mutated());
assert!(cfgset.is_okay());
assert_eq!(cfgset.cfg.bgsave, BGSave::Enabled(128));
}
#[test]
fn test_config_file_custom_bgsave() {
let file = get_toml_from_examples_dir("withcustombgsave.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn bgsave_fail() {
let mut cfgset = Configset::new_env();
cfgset.bgsave_settings(
Some("truee"),
"SKY_BGSAVE_ENABLED",
Some("128"),
"SKY_BGSAVE_DURATION",
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::new(true, 600),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
cfgset.estack[0],
"Bad value for `SKY_BGSAVE_ENABLED`. Expected true/false"
);
assert_eq!(cfgset.cfg.bgsave, BGSave::Enabled(128));
}
// snapshot settings
#[test]
fn test_config_file_bgsave_enabled_only() {
/*
* This test demonstrates a case where the user just said that BGSAVE is enabled.
* In that case, we will default to the 120 second duration
*/
let file = get_toml_from_examples_dir("bgsave-justenabled.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn snapshot_okay() {
let mut cfgset = Configset::new_env();
cfgset.snapshot_settings(
Some("3600"),
"SKY_SNAPSHOT_EVERY",
Some("0"),
"SKY_SNAPSHOT_ATMOST",
Some("false"),
"SKY_SNAPSHOT_FAILSAFE",
);
assert!(cfgset.is_mutated());
assert!(cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
)
cfgset.cfg.snapshot,
SnapshotConfig::Enabled(SnapshotPref::new(3600, 0, false))
);
}
#[test]
fn test_config_file_bgsave_every_only() {
/*
* This test demonstrates a case where the user just gave the value for every
* In that case, it means BGSAVE is enabled and set to `every` seconds
*/
let file = get_toml_from_examples_dir("bgsave-justevery.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn snapshot_fail() {
let mut cfgset = Configset::new_env();
cfgset.snapshot_settings(
Some("3600"),
"SKY_SNAPSHOT_EVERY",
Some("0"),
"SKY_SNAPSHOT_ATMOST",
Some("falsee"),
"SKY_SNAPSHOT_FAILSAFE",
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::new(true, 600),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
)
cfgset.estack[0],
"Bad value for `SKY_SNAPSHOT_FAILSAFE`. Expected true/false"
);
assert_eq!(
cfgset.cfg.snapshot,
SnapshotConfig::Enabled(SnapshotPref::new(3600, 0, true))
);
}
#[test]
fn test_config_file_snapshot() {
let file = get_toml_from_examples_dir("snapshot.toml".to_owned()).unwrap();
let cfg = ConfigurationSet::new_from_toml_str(file).unwrap();
fn snapshot_fail_with_missing_required_values() {
let mut cfgset = Configset::new_env();
cfgset.snapshot_settings(
Some("3600"),
"SKY_SNAPSHOT_EVERY",
None,
"SKY_SNAPSHOT_ATMOST",
None,
"SKY_SNAPSHOT_FAILSAFE",
);
assert!(cfgset.is_mutated());
assert!(!cfgset.is_okay());
assert_eq!(
cfg,
ConfigurationSet {
snapshot: SnapshotConfig::Enabled(SnapshotPref::new(3600, 4, true)),
bgsave: BGSave::default(),
noart: false,
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT
}
cfgset.estack[0],
"To use snapshots, pass values for both `SKY_SNAPSHOT_EVERY` and `SKY_SNAPSHOT_ATMOST`"
);
assert_eq!(cfgset.cfg.snapshot, SnapshotConfig::Disabled);
}
// TLS settings
#[test]
fn test_cli_args_conflict() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = ["skyd", "--nosave", "-c config.toml"];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let err = super::get_config_file_or_return_cfg_from_matches(matches).unwrap_err();
assert_eq!(err, ConfigError::CfgError(ERR_CONFLICT));
fn tls_settings_okay() {
let mut cfg = Configset::new_env();
cfg.tls_settings(
Some("key.pem"),
"SKY_TLS_KEY",
Some("cert.pem"),
"SKY_TLS_CERT",
Some("2005"),
"SKY_TLS_PORT",
Some("false"),
"SKY_TLS_ONLY",
None,
"SKY_TLS_PASSIN",
);
assert!(cfg.is_mutated());
assert!(cfg.is_okay());
assert_eq!(cfg.cfg.ports, {
let mut pf = PortConfig::default();
pf.upgrade_to_tls(SslOpts::new(
"key.pem".to_owned(),
"cert.pem".to_owned(),
2005,
None,
));
pf
});
}
#[test]
fn test_cli_args_conflict_with_restore_file_okay() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = ["skyd", "--restore", "somedir", "-c", "config.toml"];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let ret = super::get_config_file_or_return_cfg_from_matches(matches).unwrap_err();
// this should only compain about the missing dir but not about conflict
assert_eq!(
ret,
ConfigError::OSError(std::io::Error::from(std::io::ErrorKind::NotFound))
fn tls_settings_fail() {
let mut cfg = Configset::new_env();
cfg.tls_settings(
Some("key.pem"),
"SKY_TLS_KEY",
Some("cert.pem"),
"SKY_TLS_CERT",
Some("A2005"),
"SKY_TLS_PORT",
Some("false"),
"SKY_TLS_ONLY",
None,
"SKY_TLS_PASSIN",
);
assert!(cfg.is_mutated());
assert!(!cfg.is_okay());
assert_eq!(cfg.cfg.ports, {
let mut pf = PortConfig::default();
pf.upgrade_to_tls(SslOpts::new(
"key.pem".to_owned(),
"cert.pem".to_owned(),
2004,
None,
));
pf
});
}
#[test]
fn test_cli_args_conflict_with_restore_file_fail() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = [
"skyd",
"--restore",
"somedir",
"-c",
"config.toml",
"--nosave",
];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let ret = super::get_config_file_or_return_cfg_from_matches(matches).unwrap_err();
// this should only compain about the missing dir but not about conflict
assert_eq!(ret, ConfigError::CfgError(ERR_CONFLICT));
fn tls_settings_fail_with_missing_required_values() {
let mut cfg = Configset::new_env();
cfg.tls_settings(
Some("key.pem"),
"SKY_TLS_KEY",
None,
"SKY_TLS_CERT",
Some("2005"),
"SKY_TLS_PORT",
Some("false"),
"SKY_TLS_ONLY",
None,
"SKY_TLS_PASSIN",
);
assert!(cfg.is_mutated());
assert!(!cfg.is_okay());
assert_eq!(cfg.cfg.ports, PortConfig::default());
}
/// Gets a `toml` file from `WORKSPACEROOT/examples/config-files`
fn get_toml_from_examples_dir(filename: &str) -> TResult<String> {
use std::path;
let curdir = path::Path::new(env!("CARGO_MANIFEST_DIR"));
let workspaceroot = curdir.ancestors().nth(1).unwrap();
let mut fileloc = path::PathBuf::from(workspaceroot);
fileloc.push("examples");
fileloc.push("config-files");
fileloc.push(filename);
Ok(fs::read_to_string(fileloc)?)
}
mod cfg_file_tests {
use super::get_toml_from_examples_dir;
use crate::config::{
cfgfile, BGSave, Configset, ConfigurationSet, Modeset, PortConfig, SnapshotConfig,
SnapshotPref, SslOpts, DEFAULT_IPV4, DEFAULT_PORT,
};
use crate::dbnet::MAXIMUM_CONNECTION_LIMIT;
use std::net::{IpAddr, Ipv6Addr};
fn cfgset_from_toml_str(file: String) -> Result<Configset, toml::de::Error> {
let toml = toml::from_str(&file)?;
Ok(cfgfile::from_file(toml))
}
#[test]
fn config_file_okay() {
let file = get_toml_from_examples_dir("template.toml").unwrap();
let toml = toml::from_str(&file).unwrap();
let cfg_from_file = cfgfile::from_file(toml);
assert!(cfg_from_file.is_mutated());
assert!(cfg_from_file.is_okay());
// expected
let mut expected = ConfigurationSet::default();
expected.snapshot = SnapshotConfig::Enabled(SnapshotPref::new(3600, 4, true));
expected.ports = PortConfig::new_secure_only(
crate::config::DEFAULT_IPV4,
SslOpts::new(
"/path/to/keyfile.pem".to_owned(),
"/path/to/chain.pem".to_owned(),
2004,
Some("/path/to/cert/passphrase.txt".to_owned()),
),
);
// check
assert_eq!(cfg_from_file.cfg, expected);
}
#[test]
fn test_config_file_ok() {
let file = get_toml_from_examples_dir("skyd.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(cfg.cfg, ConfigurationSet::default());
}
#[test]
fn test_config_file_noart() {
let file = get_toml_from_examples_dir("secure-noart.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
noart: true,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
);
}
#[test]
fn test_config_file_ipv6() {
let file = get_toml_from_examples_dir("ipv6.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::new_insecure_only(
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1)),
DEFAULT_PORT
),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
);
}
#[test]
fn test_config_file_template() {
let file = get_toml_from_examples_dir("template.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet::new(
false,
BGSave::default(),
SnapshotConfig::Enabled(SnapshotPref::new(3600, 4, true)),
PortConfig::new_secure_only(
DEFAULT_IPV4,
SslOpts::new(
"/path/to/keyfile.pem".into(),
"/path/to/chain.pem".into(),
2004,
Some("/path/to/cert/passphrase.txt".to_owned())
)
),
MAXIMUM_CONNECTION_LIMIT,
Modeset::Dev,
)
);
}
#[test]
fn test_config_file_bad_bgsave_section() {
let file = get_toml_from_examples_dir("badcfg2.toml").unwrap();
let cfg = cfgset_from_toml_str(file);
assert!(cfg.is_err());
}
#[test]
fn test_config_file_custom_bgsave() {
let file = get_toml_from_examples_dir("withcustombgsave.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::new(true, 600),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
);
}
#[test]
fn test_config_file_bgsave_enabled_only() {
/*
* This test demonstrates a case where the user just said that BGSAVE is enabled.
* In that case, we will default to the 120 second duration
*/
let file = get_toml_from_examples_dir("bgsave-justenabled.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::default(),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
)
}
#[test]
fn test_config_file_bgsave_every_only() {
/*
* This test demonstrates a case where the user just gave the value for every
* In that case, it means BGSAVE is enabled and set to `every` seconds
*/
let file = get_toml_from_examples_dir("bgsave-justevery.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
noart: false,
bgsave: BGSave::new(true, 600),
snapshot: SnapshotConfig::default(),
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
)
}
#[test]
fn test_config_file_snapshot() {
let file = get_toml_from_examples_dir("snapshot.toml").unwrap();
let cfg = cfgset_from_toml_str(file).unwrap();
assert_eq!(
cfg.cfg,
ConfigurationSet {
snapshot: SnapshotConfig::Enabled(SnapshotPref::new(3600, 4, true)),
bgsave: BGSave::default(),
noart: false,
ports: PortConfig::default(),
maxcon: MAXIMUM_CONNECTION_LIMIT,
mode: Modeset::Dev
}
);
}
}
mod cli_arg_tests {
use crate::config::{cfgcli, PortConfig};
use clap::{load_yaml, App};
#[test]
fn cli_args_okay() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = ["skyd", "--host", "127.0.0.2"];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let ret = cfgcli::parse_cli_args(matches);
assert_eq!(
ret.cfg.ports,
PortConfig::new_insecure_only("127.0.0.2".parse().unwrap(), 2003)
);
assert!(ret.is_mutated());
assert!(ret.is_okay());
}
#[test]
fn cli_args_okay_no_mut() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = ["skyd", "--restore", "/some/restore/path"];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let ret = cfgcli::parse_cli_args(matches);
assert!(!ret.is_mutated());
assert!(ret.is_okay());
}
#[test]
fn cli_args_fail() {
let cfg_layout = load_yaml!("../cli.yml");
let cli_args = ["skyd", "--port", "port2003"];
let matches = App::from_yaml(cfg_layout).get_matches_from(&cli_args);
let ret = cfgcli::parse_cli_args(matches);
assert!(ret.is_mutated());
assert!(!ret.is_okay());
assert_eq!(
ret.estack[0],
"Bad value for `--port`. Expected a 16-bit positive integer"
);
}
}
mod try_from_config_source_impls {
use crate::config::{cfgcli::Flag, cfgfile::Optional, TryFromConfigSource, DEFAULT_IPV4};
use std::env::{set_var, var};
use std::fmt::Debug;
const EXPECT_TRUE: bool = true;
const EXPECT_FALSE: bool = false;
const MUTATED: bool = true;
const NOT_MUTATED: bool = false;
const IS_PRESENT: bool = true;
const IS_ABSENT: bool = false;
const MUTATION_FAILURE: bool = true;
const NO_MUTATION_FAILURE: bool = false;
fn _mut_base_test_expected<T: Default + PartialEq + Debug>(
new: impl TryFromConfigSource<T>,
expected: T,
is_present: bool,
mutate_failed: bool,
has_mutated: bool,
) {
let mut default = Default::default();
let mut mutated = false;
assert_eq!(new.is_present(), is_present);
assert_eq!(new.mutate_failed(&mut default, &mut mutated), mutate_failed);
assert_eq!(mutated, has_mutated);
assert_eq!(default, expected);
}
fn _mut_base_test<T>(
new: impl TryFromConfigSource<T>,
mut default: T,
is_present: bool,
mutate_failed: bool,
has_mutated: bool,
) {
let mut mutated = false;
dbg!(new.is_present(), is_present);
assert_eq!(new.is_present(), is_present);
assert_eq!(new.mutate_failed(&mut default, &mut mutated), mutate_failed);
assert_eq!(mutated, has_mutated);
}
fn mut_test_pass<T>(new: impl TryFromConfigSource<T>, default: T) {
_mut_base_test(new, default, IS_PRESENT, NO_MUTATION_FAILURE, MUTATED)
}
fn mut_test_fail<T>(new: impl TryFromConfigSource<T>, default: T) {
_mut_base_test(new, default, IS_PRESENT, MUTATION_FAILURE, MUTATED)
}
mod env_var {
use super::*;
// test for Result<String, VarError>
#[test]
fn env_okay_ipv4() {
set_var("TEST_SKY_SYSTEM_HOST", "127.0.0.1");
mut_test_pass(var("TEST_SKY_SYSTEM_HOST"), DEFAULT_IPV4);
}
#[test]
fn env_fail_ipv4() {
set_var("TEST_SKY_SYSTEM_HOST2", "127.0.0.1A");
mut_test_fail(var("TEST_SKY_SYSTEM_HOST2"), DEFAULT_IPV4);
}
}
mod option_str {
use super::*;
// test for Option<&str> (as in CLI)
#[test]
fn option_str_okay_ipv4() {
let ip = Some("127.0.0.1");
mut_test_pass(ip, DEFAULT_IPV4);
}
#[test]
fn option_str_fail_ipv4() {
let ip = Some("127.0.0.1A");
mut_test_fail(ip, DEFAULT_IPV4);
}
#[test]
fn option_str_nomut() {
let ip = None;
_mut_base_test(
ip,
DEFAULT_IPV4,
IS_ABSENT,
NO_MUTATION_FAILURE,
NOT_MUTATED,
);
}
}
mod cfgcli_flag {
use super::*;
#[test]
fn flag_true_if_set_okay_set() {
// this is true if flag is present
let flag = Flag::<true>::new(true);
// we expect true
_mut_base_test_expected(flag, EXPECT_TRUE, IS_PRESENT, NO_MUTATION_FAILURE, MUTATED);
}
#[test]
fn flag_true_if_set_okay_unset() {
// this is true if flag is present, but the flag here is not present
let flag = Flag::<true>::new(false);
// we expect no mutation because the flag was not set
_mut_base_test(
flag,
EXPECT_FALSE,
IS_ABSENT,
NO_MUTATION_FAILURE,
NOT_MUTATED,
);
}
#[test]
fn flag_false_if_set_okay_set() {
// this is false if flag is present
let flag = Flag::<false>::new(true);
// expect mutation to have happened
_mut_base_test_expected(flag, EXPECT_FALSE, IS_PRESENT, NO_MUTATION_FAILURE, MUTATED);
}
#[test]
fn flag_false_if_set_okay_unset() {
// this is false if flag is present, but the flag is absent
let flag = Flag::<true>::new(false);
// expect no mutation
_mut_base_test(
flag,
EXPECT_FALSE,
IS_ABSENT,
NO_MUTATION_FAILURE,
NOT_MUTATED,
);
}
}
mod optional {
use super::*;
// test for cfg file scenario
#[test]
fn optional_okay_ipv4() {
let ip = Optional::some(DEFAULT_IPV4);
mut_test_pass(ip, DEFAULT_IPV4);
}
#[test]
fn optional_okay_ipv4_none() {
let ip = Optional::from(None);
_mut_base_test(
ip,
DEFAULT_IPV4,
IS_ABSENT,
NO_MUTATION_FAILURE,
NOT_MUTATED,
);
}
}
mod cfgfile_nonull {
use super::*;
use crate::config::cfgfile::NonNull;
#[test]
fn nonnull_okay() {
let port = NonNull::from(2100);
_mut_base_test_expected(port, 2100, IS_PRESENT, NO_MUTATION_FAILURE, MUTATED);
}
}
mod optstring {
use super::*;
use crate::config::OptString;
#[test]
fn optstring_okay() {
let pass = OptString::from(Some("tlspass.txt".to_owned()));
_mut_base_test_expected(
pass,
OptString::from(Some("tlspass.txt".to_owned())),
IS_PRESENT,
NO_MUTATION_FAILURE,
MUTATED,
);
}
#[test]
fn optstring_null_okay() {
let pass = OptString::from(None);
_mut_base_test_expected(
pass,
OptString::new_null(),
IS_ABSENT,
NO_MUTATION_FAILURE,
NOT_MUTATED,
);
}
}
}
mod modeset_de {
use crate::config::Modeset;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Example {
mode: Modeset,
}
#[test]
fn deserialize_modeset_prod_okay() {
#[derive(Deserialize, Debug)]
struct Example {
mode: Modeset,
}
let toml = r#"mode="prod""#;
let x: Example = toml::from_str(toml).unwrap();
assert_eq!(x.mode, Modeset::Prod);
}
#[test]
fn deserialize_modeset_user_okay() {
let toml = r#"mode="dev""#;
let x: Example = toml::from_str(toml).unwrap();
assert_eq!(x.mode, Modeset::Dev);
}
#[test]
fn deserialize_modeset_fail() {
let toml = r#"mode="superuser""#;
let e = toml::from_str::<Example>(toml).unwrap_err();
assert_eq!(
e.to_string(),
"Bad value `superuser` for modeset for key `mode` at line 1 column 6"
);
}
}

@ -46,8 +46,6 @@ use std::thread;
use std::time;
#[macro_use]
mod util;
#[macro_use]
extern crate libsky;
mod actions;
mod admin;
mod arbiter;
@ -169,28 +167,28 @@ use self::config::{BGSave, PortConfig, SnapshotConfig};
/// This function checks the command line arguments and either returns a config object
/// or prints an error to `stderr` and terminates the server
fn check_args_and_get_cfg() -> (PortConfig, BGSave, SnapshotConfig, Option<String>, usize) {
let cfg = config::get_config_file_or_return_cfg();
let binding_and_cfg = match cfg {
Ok(config::ConfigType::Custom(cfg, file)) => {
match config::get_config() {
Ok(cfg) => {
if cfg.is_artful() {
println!("Skytable v{} | {}\n{}", VERSION, URL, TEXT);
} else {
println!("Skytable v{} | {}", VERSION, URL);
}
log::info!("Using settings from supplied configuration");
(cfg.ports, cfg.bgsave, cfg.snapshot, file, cfg.maxcon)
}
Ok(config::ConfigType::Def(cfg, file)) => {
println!("Skytable v{} | {}\n{}", VERSION, URL, TEXT);
log::warn!("No configuration file supplied. Using default settings");
(cfg.ports, cfg.bgsave, cfg.snapshot, file, cfg.maxcon)
if cfg.is_custom() {
log::info!("Using settings from supplied configuration");
} else {
log::warn!("No configuration file supplied. Using default settings");
}
// print warnings if any
cfg.print_warnings();
let (cfg, restore) = cfg.finish();
(cfg.ports, cfg.bgsave, cfg.snapshot, restore, cfg.maxcon)
}
Err(e) => {
log::error!("{}", e);
std::process::exit(0x01);
}
};
binding_and_cfg
}
}
/// On startup, we attempt to check if a `.sky_pid` file exists. If it does, then

@ -57,7 +57,7 @@ pub enum UnsafeFlatElement {
}
impl UnsafeElement {
pub const fn is_any_array(&self) -> bool {
pub const fn is_any_array(&self) -> bool {
matches!(self, Self::AnyArray(_))
}
}

@ -0,0 +1,68 @@
/*
* Created on Sat Jan 29 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
//! Dark compiler arts and hackery to defy the normal. Use at your own
//! risk
use core::mem;
#[cold]
#[inline(never)]
pub const fn cold() {}
pub const fn likely(b: bool) -> bool {
if !b {
cold()
}
b
}
pub const fn unlikely(b: bool) -> bool {
if b {
cold()
}
b
}
#[cold]
#[inline(never)]
pub const fn cold_err<T>(v: T) -> T {
v
}
#[inline(always)]
pub const fn hot<T>(v: T) -> T {
if false {
cold()
}
v
}
pub const unsafe fn extend_lifetime<'a, 'b, T>(inp: &'a T) -> &'b T {
mem::transmute(inp)
}
pub unsafe fn extend_lifetime_mut<'a, 'b, T>(inp: &'a mut T) -> &'b mut T {
mem::transmute(inp)
}

@ -1,5 +1,5 @@
/*
* Created on Fri Jun 25 2021
* Created on Sat Jan 29 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
@ -7,7 +7,7 @@
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2021, Sayan Nandan <ohsayan@outlook.com>
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -24,22 +24,6 @@
*
*/
/// # Unsafe unwrapping
///
/// This trait provides a method `unsafe_unwrap` that is potentially unsafe and has
/// the ability to **violate multiple safety gurantees** that rust provides. So,
/// if you get `SIGILL`s or `SIGSEGV`s, by using this trait, blame yourself.
pub unsafe trait Unwrappable<T> {
/// Unwrap a _nullable_ (almost) type to get its value while asserting that the value
/// cannot ever be null
///
/// ## Safety
/// The trait is unsafe, and so is this function. You can wreck potential havoc if you
/// use this heedlessly
///
unsafe fn unsafe_unwrap(self) -> T;
}
#[macro_export]
macro_rules! impossible {
() => {
@ -47,24 +31,6 @@ macro_rules! impossible {
};
}
unsafe impl<T, E> Unwrappable<T> for Result<T, E> {
unsafe fn unsafe_unwrap(self) -> T {
match self {
Ok(t) => t,
Err(_) => impossible!(),
}
}
}
unsafe impl<T> Unwrappable<T> for Option<T> {
unsafe fn unsafe_unwrap(self) -> T {
match self {
Some(t) => t,
None => impossible!(),
}
}
}
#[macro_export]
macro_rules! consts {
($($(#[$attr:meta])* $ident:ident : $ty:ty = $expr:expr;)*) => {
@ -195,51 +161,6 @@ macro_rules! afn_action {
};
}
pub mod compiler {
//! Dark compiler arts and hackery to defy the normal. Use at your own
//! risk
use core::mem;
#[cold]
#[inline(never)]
pub const fn cold() {}
pub const fn likely(b: bool) -> bool {
if !b {
cold()
}
b
}
pub const fn unlikely(b: bool) -> bool {
if b {
cold()
}
b
}
#[cold]
#[inline(never)]
pub const fn cold_err<T>(v: T) -> T {
v
}
#[inline(always)]
pub const fn hot<T>(v: T) -> T {
if false {
cold()
}
v
}
pub unsafe fn extend_lifetime<'a, 'b, T>(inp: &'a T) -> &'b T {
mem::transmute(inp)
}
pub unsafe fn extend_lifetime_mut<'a, 'b, T>(inp: &'a mut T) -> &'b mut T {
mem::transmute(inp)
}
}
#[macro_export]
macro_rules! byt {
($f:expr) => {

@ -1,5 +1,5 @@
/*
* Created on Sat Oct 02 2021
* Created on Fri Jun 25 2021
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
@ -24,36 +24,41 @@
*
*/
macro_rules! cli_parse_or_default_or_err {
($parsewhat:expr, $default:expr, $except:expr $(,)?) => {
match $parsewhat.map(|v| v.parse()) {
Some(Ok(v)) => v,
Some(Err(_)) => return Err(self::cfgerr::ConfigError::CliArgErr($except)),
None => $default,
}
};
}
#[macro_use]
mod macros;
pub mod compiler;
pub mod os;
macro_rules! cli_setparse_or_err {
($setwhat:expr, $parsewhat:expr, $except:expr $(,)?) => {
match $parsewhat.map(|v| v.parse()) {
Some(Ok(v)) => $setwhat = v,
Some(Err(_)) => return Err(self::cfgerr::ConfigError::CliArgErr($except)),
_ => {}
}
};
/// # Unsafe unwrapping
///
/// This trait provides a method `unsafe_unwrap` that is potentially unsafe and has
/// the ability to **violate multiple safety gurantees** that rust provides. So,
/// if you get `SIGILL`s or `SIGSEGV`s, by using this trait, blame yourself.
pub unsafe trait Unwrappable<T> {
/// Unwrap a _nullable_ (almost) type to get its value while asserting that the value
/// cannot ever be null
///
/// ## Safety
/// The trait is unsafe, and so is this function. You can wreck potential havoc if you
/// use this heedlessly
///
unsafe fn unsafe_unwrap(self) -> T;
}
macro_rules! set_if_exists {
($testwhat:expr, $trywhat:expr) => {
if let Some(testwhat) = $testwhat {
$trywhat = testwhat;
unsafe impl<T, E> Unwrappable<T> for Result<T, E> {
unsafe fn unsafe_unwrap(self) -> T {
match self {
Ok(t) => t,
Err(_) => impossible!(),
}
};
}
}
macro_rules! ret_cli_err {
($what:expr) => {
return Err(self::cfgerr::ConfigError::CliArgErr($what))
};
unsafe impl<T> Unwrappable<T> for Option<T> {
unsafe fn unsafe_unwrap(self) -> T {
match self {
Some(t) => t,
None => impossible!(),
}
}
}

@ -0,0 +1,80 @@
/*
* Created on Sat Jan 29 2022
*
* This file is a part of Skytable
* Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source
* NoSQL database written by Sayan Nandan ("the Author") with the
* vision to provide flexibility in data modelling without compromising
* on performance, queryability or scalability.
*
* Copyright (c) 2022, Sayan Nandan <ohsayan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#[cfg(unix)]
pub use unix::*;
#[cfg(unix)]
mod unix {
use libc::{rlimit, RLIMIT_NOFILE};
use std::io::Error as IoError;
#[derive(Debug)]
pub struct ResourceLimit {
cur: u64,
max: u64,
}
impl ResourceLimit {
const fn new(cur: u64, max: u64) -> Self {
Self { cur, max }
}
pub const fn is_over_limit(&self, expected: usize) -> bool {
expected as u64 > self.cur
}
/// Returns the maximum number of open files
pub fn get() -> Result<Self, IoError> {
unsafe {
let rlim = rlimit {
rlim_cur: 0,
rlim_max: 0,
};
let ret = libc::getrlimit(RLIMIT_NOFILE, &rlim as *const _ as *mut _);
if ret != 0 {
Err(IoError::last_os_error())
} else {
Ok(ResourceLimit::new(
rlim.rlim_cur.into(),
rlim.rlim_max.into(),
))
}
}
}
/// Returns the current limit
pub const fn current(&self) -> u64 {
self.cur
}
/// Returns the max limit
pub const fn max(&self) -> u64 {
self.max
}
}
#[test]
fn test_ulimit() {
let _ = ResourceLimit::get().unwrap();
}
}

@ -11,8 +11,8 @@ version = "0.7.2"
libstress = { path = "../libstress" }
skytable = { git = "https://github.com/skytable/client-rust", branch = "next" }
# external deps
clap = { version = "2.34.0", features = ["yaml"] }
clap = { version = "2", features = ["yaml"] }
devtimer = "4.0.1"
rand = "0.8.4"
serde = { version = "1.0.133", features = ["derive"] }
serde_json = "1.0.75"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.78"

@ -12,6 +12,6 @@ proc-macro = true
[dependencies]
# external deps
proc-macro2 = "1.0.36"
quote = "1.0.14"
quote = "1.0.15"
rand = "0.8.4"
syn = { version = "1.0.85", features = ["full"] }
syn = { version = "1.0.86", features = ["full"] }

@ -11,4 +11,4 @@ skytable = { git = "https://github.com/skytable/client-rust.git" }
env_logger = "0.9.0"
bincode = "1.3.3"
log = "0.4.14"
clap = { version = "2.34.0", features = ["yaml"] }
clap = { version = "2", features = ["yaml"] }

Loading…
Cancel
Save