mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-15 09:22:55 +00:00
This PR simplifies the pageserver configuration parsing as follows:
* introduce the `pageserver_api::config::ConfigToml` type
* implement `Default` for `ConfigToml`
* use serde derive to do the brain-dead leg-work of processing the toml
document
* use `serde(default)` to fill in default values
* in `pageserver` crate:
* use `toml_edit` to deserialize the pageserver.toml string into a
`ConfigToml`
* `PageServerConfig::parse_and_validate` then
* consumes the `ConfigToml`
* destructures it exhaustively into its constituent fields
* constructs the `PageServerConfig`
The rules are:
* in `ConfigToml`, use `deny_unknown_fields` everywhere
* static default values go in `pageserver_api`
* if there cannot be a static default value (e.g. which default IO
engine to use, because it depends on the runtime), make the field in
`ConfigToml` an `Option`
* if runtime-augmentation of a value is needed, do that in
`parse_and_validate`
* a good example is `virtual_file_io_engine` or `l0_flush`, both of
which need to execute code to determine the effective value in
`PageServerConf`
The benefits:
* massive amount of brain-dead repetitive code can be deleted
* "unused variable" compile-time errors when removing a config value,
due to the exhaustive destructuring in `parse_and_validate`
* compile-time errors guide you when adding a new config field
Drawbacks:
* serde derive is sometimes a bit too magical
* `deny_unknown_fields` is easy to miss
Future Work / Benefits:
* make `neon_local` use `pageserver_api` to construct `ConfigToml` and
write it to `pageserver.toml`
* This provides more type safety / coompile-time errors than the current
approach.
### Refs
Fixes #3682
### Future Work
* `remote_storage` deser doesn't reject unknown fields
https://github.com/neondatabase/neon/issues/8915
* clean up `libs/pageserver_api/src/config.rs` further
* break up into multiple files, at least for tenant config
* move `models` as appropriate / refine distinction between config and
API models / be explicit about when it's the same
* use `pub(crate)` visibility on `mod defaults` to detect stale values
144 lines
4.5 KiB
Rust
144 lines
4.5 KiB
Rust
//! Wrapper around nix::sys::statvfs::Statvfs that allows for mocking.
|
|
|
|
use camino::Utf8Path;
|
|
|
|
pub enum Statvfs {
|
|
Real(nix::sys::statvfs::Statvfs),
|
|
Mock(mock::Statvfs),
|
|
}
|
|
|
|
// NB: on macOS, the block count type of struct statvfs is u32.
|
|
// The workaround seems to be to use the non-standard statfs64 call.
|
|
// Sincce it should only be a problem on > 2TiB disks, let's ignore
|
|
// the problem for now and upcast to u64.
|
|
impl Statvfs {
|
|
pub fn get(tenants_dir: &Utf8Path, mocked: Option<&mock::Behavior>) -> nix::Result<Self> {
|
|
if let Some(mocked) = mocked {
|
|
Ok(Statvfs::Mock(mock::get(tenants_dir, mocked)?))
|
|
} else {
|
|
Ok(Statvfs::Real(nix::sys::statvfs::statvfs(
|
|
tenants_dir.as_std_path(),
|
|
)?))
|
|
}
|
|
}
|
|
|
|
// NB: allow() because the block count type is u32 on macOS.
|
|
#[allow(clippy::useless_conversion, clippy::unnecessary_fallible_conversions)]
|
|
pub fn blocks(&self) -> u64 {
|
|
match self {
|
|
Statvfs::Real(stat) => u64::try_from(stat.blocks()).unwrap(),
|
|
Statvfs::Mock(stat) => stat.blocks,
|
|
}
|
|
}
|
|
|
|
// NB: allow() because the block count type is u32 on macOS.
|
|
#[allow(clippy::useless_conversion, clippy::unnecessary_fallible_conversions)]
|
|
pub fn blocks_available(&self) -> u64 {
|
|
match self {
|
|
Statvfs::Real(stat) => u64::try_from(stat.blocks_available()).unwrap(),
|
|
Statvfs::Mock(stat) => stat.blocks_available,
|
|
}
|
|
}
|
|
|
|
pub fn fragment_size(&self) -> u64 {
|
|
match self {
|
|
Statvfs::Real(stat) => stat.fragment_size(),
|
|
Statvfs::Mock(stat) => stat.fragment_size,
|
|
}
|
|
}
|
|
|
|
pub fn block_size(&self) -> u64 {
|
|
match self {
|
|
Statvfs::Real(stat) => stat.block_size(),
|
|
Statvfs::Mock(stat) => stat.block_size,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod mock {
|
|
use camino::Utf8Path;
|
|
use regex::Regex;
|
|
use tracing::log::info;
|
|
|
|
pub use pageserver_api::config::statvfs::mock::Behavior;
|
|
|
|
pub fn get(tenants_dir: &Utf8Path, behavior: &Behavior) -> nix::Result<Statvfs> {
|
|
info!("running mocked statvfs");
|
|
|
|
match behavior {
|
|
Behavior::Success {
|
|
blocksize,
|
|
total_blocks,
|
|
ref name_filter,
|
|
} => {
|
|
let used_bytes = walk_dir_disk_usage(tenants_dir, name_filter.as_deref()).unwrap();
|
|
|
|
// round it up to the nearest block multiple
|
|
let used_blocks = (used_bytes + (blocksize - 1)) / blocksize;
|
|
|
|
if used_blocks > *total_blocks {
|
|
panic!(
|
|
"mocking error: used_blocks > total_blocks: {used_blocks} > {total_blocks}"
|
|
);
|
|
}
|
|
|
|
let avail_blocks = total_blocks - used_blocks;
|
|
|
|
Ok(Statvfs {
|
|
blocks: *total_blocks,
|
|
blocks_available: avail_blocks,
|
|
fragment_size: *blocksize,
|
|
block_size: *blocksize,
|
|
})
|
|
}
|
|
#[cfg(feature = "testing")]
|
|
Behavior::Failure { mocked_error } => Err((*mocked_error).into()),
|
|
}
|
|
}
|
|
|
|
fn walk_dir_disk_usage(path: &Utf8Path, name_filter: Option<&Regex>) -> anyhow::Result<u64> {
|
|
let mut total = 0;
|
|
for entry in walkdir::WalkDir::new(path) {
|
|
let entry = entry?;
|
|
if !entry.file_type().is_file() {
|
|
continue;
|
|
}
|
|
if !name_filter
|
|
.as_ref()
|
|
.map(|filter| filter.is_match(entry.file_name().to_str().unwrap()))
|
|
.unwrap_or(true)
|
|
{
|
|
continue;
|
|
}
|
|
let m = match entry.metadata() {
|
|
Ok(m) => m,
|
|
Err(e) if is_not_found(&e) => {
|
|
// some temp file which got removed right as we are walking
|
|
continue;
|
|
}
|
|
Err(e) => {
|
|
return Err(anyhow::Error::new(e)
|
|
.context(format!("get metadata of {:?}", entry.path())))
|
|
}
|
|
};
|
|
total += m.len();
|
|
}
|
|
Ok(total)
|
|
}
|
|
|
|
fn is_not_found(e: &walkdir::Error) -> bool {
|
|
let Some(io_error) = e.io_error() else {
|
|
return false;
|
|
};
|
|
let kind = io_error.kind();
|
|
matches!(kind, std::io::ErrorKind::NotFound)
|
|
}
|
|
|
|
pub struct Statvfs {
|
|
pub blocks: u64,
|
|
pub blocks_available: u64,
|
|
pub fragment_size: u64,
|
|
pub block_size: u64,
|
|
}
|
|
}
|