mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-14 00:42:54 +00:00
Move tenant_config code to a separate module.
Add checksum to tenantconf file. Save tenant conf file to tenant's directory, when tenant is created.
This commit is contained in:
@@ -5,25 +5,19 @@
|
||||
//! See also `settings.md` for better description on every parameter.
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use toml_edit;
|
||||
use toml_edit::{Document, Item};
|
||||
use tracing::*;
|
||||
use zenith_utils::bin_ser::BeSer;
|
||||
use zenith_utils::postgres_backend::AuthType;
|
||||
use zenith_utils::zid::{ZNodeId, ZTenantId, ZTimelineId};
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::num::{NonZeroU32, NonZeroUsize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::layered_repository::TIMELINES_SEGMENT_NAME;
|
||||
use crate::virtual_file::VirtualFile;
|
||||
|
||||
pub mod defaults {
|
||||
use const_format::formatcp;
|
||||
@@ -145,60 +139,6 @@ pub struct PageServerConf {
|
||||
pub remote_storage_config: Option<RemoteStorageConfig>,
|
||||
}
|
||||
|
||||
pub const TENANT_CONFIG_NAME: &str = "config";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TenantConf {
|
||||
pub checkpoint_distance: u64,
|
||||
pub compaction_target_size: u64,
|
||||
pub compaction_period: Duration,
|
||||
pub gc_horizon: u64,
|
||||
pub gc_period: Duration,
|
||||
pub pitr_interval: Duration,
|
||||
}
|
||||
|
||||
impl TenantConf {
|
||||
pub fn from(conf: &PageServerConf) -> TenantConf {
|
||||
TenantConf {
|
||||
gc_period: conf.gc_period,
|
||||
gc_horizon: conf.gc_horizon,
|
||||
pitr_interval: conf.pitr_interval,
|
||||
checkpoint_distance: conf.checkpoint_distance,
|
||||
compaction_period: conf.compaction_period,
|
||||
compaction_target_size: conf.compaction_target_size,
|
||||
}
|
||||
}
|
||||
pub fn save(&self, conf: &'static PageServerConf, tenantid: ZTenantId) -> Result<()> {
|
||||
let _enter = info_span!("saving tenant config").entered();
|
||||
let path = conf.tenant_path(&tenantid).join(TENANT_CONFIG_NAME);
|
||||
let mut file =
|
||||
VirtualFile::open_with_options(&path, OpenOptions::new().write(true).create_new(true))?;
|
||||
let config_bytes = self.ser()?;
|
||||
if file.write(&config_bytes)? != config_bytes.len() {
|
||||
bail!("Could not write all the metadata bytes in a single call");
|
||||
}
|
||||
file.sync_all()?;
|
||||
let tenant_dir = File::open(
|
||||
&path
|
||||
.parent()
|
||||
.expect("Tetant config should always have a parent dir"),
|
||||
)?;
|
||||
tenant_dir.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(conf: &'static PageServerConf, tenantid: ZTenantId) -> Result<TenantConf> {
|
||||
let _enter = info_span!("loading tenant config").entered();
|
||||
let path = conf.tenant_path(&tenantid).join(TENANT_CONFIG_NAME);
|
||||
let content = std::fs::read(&path);
|
||||
match content {
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(Self::from(conf)),
|
||||
Ok(config_bytes) => Ok(TenantConf::des(&config_bytes)?),
|
||||
Err(err) => bail!(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use dedicated enum for builder to better indicate the intention
|
||||
// and avoid possible confusion with nested options
|
||||
pub enum BuilderValue<T> {
|
||||
|
||||
@@ -23,8 +23,9 @@ use super::models::{
|
||||
};
|
||||
use crate::remote_storage::{schedule_timeline_download, RemoteIndex};
|
||||
use crate::repository::Repository;
|
||||
use crate::tenant_config::TenantConf;
|
||||
use crate::timelines::{LocalTimelineInfo, RemoteTimelineInfo, TimelineInfo};
|
||||
use crate::{config::PageServerConf, config::TenantConf, tenant_mgr, timelines, ZTenantId};
|
||||
use crate::{config::PageServerConf, tenant_mgr, timelines, ZTenantId};
|
||||
|
||||
struct State {
|
||||
conf: &'static PageServerConf,
|
||||
|
||||
@@ -32,8 +32,9 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, TryLockError};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use self::metadata::{metadata_path, TimelineMetadata, METADATA_FILE_NAME};
|
||||
use crate::config::{PageServerConf, TenantConf};
|
||||
use crate::config::PageServerConf;
|
||||
use crate::keyspace::KeySpace;
|
||||
use crate::tenant_config::{TenantConf, TenantConfFile};
|
||||
|
||||
use crate::page_cache;
|
||||
use crate::remote_storage::{schedule_timeline_checkpoint_upload, RemoteIndex};
|
||||
@@ -566,6 +567,58 @@ impl LayeredRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// Save tenant's config to file
|
||||
pub fn save_tenantconf(
|
||||
conf: &'static PageServerConf,
|
||||
tenantid: ZTenantId,
|
||||
tenant_conf: TenantConf,
|
||||
first_save: bool,
|
||||
) -> Result<()> {
|
||||
let _enter = info_span!("saving tenantconf").entered();
|
||||
let path = TenantConf::tenantconf_path(conf, tenantid);
|
||||
info!("save tenantconf to {}", path.display());
|
||||
|
||||
// use OpenOptions to ensure file presence is consistent with first_save
|
||||
let mut file = VirtualFile::open_with_options(
|
||||
&path,
|
||||
OpenOptions::new().write(true).create_new(first_save),
|
||||
)?;
|
||||
|
||||
let data = TenantConfFile::from(tenant_conf);
|
||||
let tenantconf_bytes = data.to_bytes().context("Failed to get tenantconf bytes")?;
|
||||
|
||||
if file.write(&tenantconf_bytes)? != tenantconf_bytes.len() {
|
||||
bail!("Could not write all the tenantconf bytes in a single call");
|
||||
}
|
||||
file.sync_all()?;
|
||||
|
||||
// fsync the parent directory to ensure the directory entry is durable
|
||||
if first_save {
|
||||
let tenant_dir = File::open(
|
||||
&path
|
||||
.parent()
|
||||
.expect("Tenantconf should always have a parent dir"),
|
||||
)?;
|
||||
tenant_dir.sync_all()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_tenantconf(
|
||||
conf: &'static PageServerConf,
|
||||
tenantid: ZTenantId,
|
||||
) -> Result<TenantConf> {
|
||||
let path = TenantConf::tenantconf_path(conf, tenantid);
|
||||
info!("loading tenantconf from {}", path.display());
|
||||
let tenantconf_bytes = std::fs::read(&path)?;
|
||||
let tenant_conf_file = TenantConfFile::from_bytes(&tenantconf_bytes);
|
||||
match tenant_conf_file {
|
||||
Ok(tenant_conf) => return Ok(tenant_conf.body),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
}
|
||||
|
||||
/// Save timeline metadata to file
|
||||
fn save_metadata(
|
||||
conf: &'static PageServerConf,
|
||||
@@ -2254,10 +2307,6 @@ pub mod tests {
|
||||
}
|
||||
|
||||
let cutoff = tline.get_last_record_lsn();
|
||||
let parts = keyspace
|
||||
.clone()
|
||||
.to_keyspace()
|
||||
.partition(TEST_FILE_SIZE as u64);
|
||||
|
||||
tline.update_gc_info(Vec::new(), cutoff, Duration::ZERO);
|
||||
tline.checkpoint(CheckpointConfig::Forced)?;
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod pgdatadir_mapping;
|
||||
pub mod reltag;
|
||||
pub mod remote_storage;
|
||||
pub mod repository;
|
||||
pub mod tenant_config;
|
||||
pub mod tenant_mgr;
|
||||
pub mod tenant_threads;
|
||||
pub mod thread_mgr;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::TenantConf;
|
||||
use crate::layered_repository::metadata::TimelineMetadata;
|
||||
use crate::remote_storage::RemoteIndex;
|
||||
use crate::tenant_config::TenantConf;
|
||||
use crate::walrecord::ZenithWalRecord;
|
||||
use crate::CheckpointConfig;
|
||||
use anyhow::{bail, Result};
|
||||
@@ -511,6 +511,7 @@ pub mod repo_harness {
|
||||
|
||||
let repo = LayeredRepository::new(
|
||||
self.conf,
|
||||
// Use default TenantConf in tests
|
||||
TenantConf::from(self.conf),
|
||||
walredo_mgr,
|
||||
self.tenant_id,
|
||||
|
||||
185
pageserver/src/tenant_config.rs
Normal file
185
pageserver/src/tenant_config.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
//! Functions for handling per-tenant configuration options
|
||||
//!
|
||||
//! If tenant is created with --config option,
|
||||
//! the tenant-specific config will be stored in tenant's directory.
|
||||
//! Otherwise, global pageserver's config is used.
|
||||
//!
|
||||
//! If the tenant config file is corrupted, the tenant will be disabled.
|
||||
//! We cannot use global or default config instead, because wrong settings
|
||||
//! may lead to a data loss.
|
||||
//!
|
||||
use crate::config::PageServerConf;
|
||||
use crate::STORAGE_FORMAT_VERSION;
|
||||
use anyhow::ensure;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use zenith_utils::bin_ser::BeSer;
|
||||
use zenith_utils::zid::ZTenantId;
|
||||
|
||||
pub const TENANT_CONFIG_NAME: &str = "config";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TenantConf {
|
||||
pub checkpoint_distance: u64,
|
||||
pub compaction_target_size: u64,
|
||||
pub compaction_period: Duration,
|
||||
pub gc_horizon: u64,
|
||||
pub gc_period: Duration,
|
||||
pub pitr_interval: Duration,
|
||||
}
|
||||
|
||||
/// We assume that a write of up to TENANTCONF_MAX_SIZE bytes is atomic.
|
||||
///
|
||||
/// This is the same assumption that PostgreSQL makes with the control file,
|
||||
/// see PG_CONTROL_MAX_SAFE_SIZE
|
||||
const TENANTCONF_MAX_SIZE: usize = 512;
|
||||
|
||||
/// TenantConfFile is stored on disk in tenant's directory
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TenantConfFile {
|
||||
hdr: TenantConfHeader,
|
||||
pub body: TenantConf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct TenantConfHeader {
|
||||
checksum: u32, // CRC of serialized tenantconf body
|
||||
size: u16, // size of serialized tenantconf
|
||||
format_version: u16, // storage format version (used for compatibility checks)
|
||||
}
|
||||
const TENANTCONF_HDR_SIZE: usize = std::mem::size_of::<TenantConfHeader>();
|
||||
|
||||
impl TenantConfFile {
|
||||
pub fn new(
|
||||
checkpoint_distance: u64,
|
||||
compaction_target_size: u64,
|
||||
compaction_period: Duration,
|
||||
gc_horizon: u64,
|
||||
gc_period: Duration,
|
||||
pitr_interval: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
hdr: TenantConfHeader {
|
||||
checksum: 0,
|
||||
size: 0,
|
||||
format_version: STORAGE_FORMAT_VERSION,
|
||||
},
|
||||
body: TenantConf {
|
||||
gc_period,
|
||||
gc_horizon,
|
||||
pitr_interval,
|
||||
checkpoint_distance,
|
||||
compaction_period,
|
||||
compaction_target_size,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from(tconf: TenantConf) -> Self {
|
||||
Self {
|
||||
hdr: TenantConfHeader {
|
||||
checksum: 0,
|
||||
size: 0,
|
||||
format_version: STORAGE_FORMAT_VERSION,
|
||||
},
|
||||
body: TenantConf {
|
||||
gc_period: tconf.gc_period,
|
||||
gc_horizon: tconf.gc_horizon,
|
||||
pitr_interval: tconf.pitr_interval,
|
||||
checkpoint_distance: tconf.checkpoint_distance,
|
||||
compaction_period: tconf.compaction_period,
|
||||
compaction_target_size: tconf.compaction_target_size,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(tenantconf_bytes: &[u8]) -> anyhow::Result<Self> {
|
||||
ensure!(
|
||||
tenantconf_bytes.len() == TENANTCONF_MAX_SIZE,
|
||||
"tenantconf bytes size is wrong"
|
||||
);
|
||||
let hdr = TenantConfHeader::des(&tenantconf_bytes[0..TENANTCONF_HDR_SIZE])?;
|
||||
ensure!(
|
||||
hdr.format_version == STORAGE_FORMAT_VERSION,
|
||||
"format version mismatch"
|
||||
);
|
||||
let tenantconf_size = hdr.size as usize;
|
||||
ensure!(
|
||||
tenantconf_size <= TENANTCONF_MAX_SIZE,
|
||||
"corrupted tenantconf file"
|
||||
);
|
||||
let calculated_checksum =
|
||||
crc32c::crc32c(&tenantconf_bytes[TENANTCONF_HDR_SIZE..tenantconf_size]);
|
||||
ensure!(
|
||||
hdr.checksum == calculated_checksum,
|
||||
"tenantconf checksum mismatch"
|
||||
);
|
||||
let body = TenantConf::des(&tenantconf_bytes[TENANTCONF_HDR_SIZE..tenantconf_size])?;
|
||||
|
||||
Ok(TenantConfFile { hdr, body })
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> anyhow::Result<Vec<u8>> {
|
||||
let body_bytes = self.body.ser()?;
|
||||
let tenantconf_size = TENANTCONF_HDR_SIZE + body_bytes.len();
|
||||
let hdr = TenantConfHeader {
|
||||
size: tenantconf_size as u16,
|
||||
format_version: STORAGE_FORMAT_VERSION,
|
||||
checksum: crc32c::crc32c(&body_bytes),
|
||||
};
|
||||
let hdr_bytes = hdr.ser()?;
|
||||
let mut tenantconf_bytes = vec![0u8; TENANTCONF_MAX_SIZE];
|
||||
tenantconf_bytes[0..TENANTCONF_HDR_SIZE].copy_from_slice(&hdr_bytes);
|
||||
tenantconf_bytes[TENANTCONF_HDR_SIZE..tenantconf_size].copy_from_slice(&body_bytes);
|
||||
Ok(tenantconf_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TenantConf {
|
||||
pub fn from(conf: &PageServerConf) -> TenantConf {
|
||||
TenantConf {
|
||||
gc_period: conf.gc_period,
|
||||
gc_horizon: conf.gc_horizon,
|
||||
pitr_interval: conf.pitr_interval,
|
||||
checkpoint_distance: conf.checkpoint_distance,
|
||||
compaction_period: conf.compaction_period,
|
||||
compaction_target_size: conf.compaction_target_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Points to a place in pageserver's local directory,
|
||||
/// where certain tenant's tenantconf file should be located.
|
||||
pub fn tenantconf_path(conf: &'static PageServerConf, tenantid: ZTenantId) -> PathBuf {
|
||||
conf.tenant_path(&tenantid).join(TENANT_CONFIG_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tenantconf_serializes_correctly() {
|
||||
let original_tenantconf = TenantConfFile::new(
|
||||
111,
|
||||
111,
|
||||
Duration::from_secs(111),
|
||||
222,
|
||||
Duration::from_secs(111),
|
||||
Duration::from_secs(60 * 60),
|
||||
);
|
||||
|
||||
let tenantconf_bytes = original_tenantconf
|
||||
.to_bytes()
|
||||
.expect("Should serialize correct tenantconf to bytes");
|
||||
|
||||
let deserialized_tenantconf = TenantConfFile::from_bytes(&tenantconf_bytes)
|
||||
.expect("Should deserialize its own bytes");
|
||||
|
||||
assert_eq!(
|
||||
deserialized_tenantconf.body, original_tenantconf.body,
|
||||
"Tenantconf that was serialized to bytes and deserialized back should not change"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
//! This module acts as a switchboard to access different repositories managed by this
|
||||
//! page server.
|
||||
|
||||
use crate::config::{PageServerConf, TenantConf};
|
||||
use crate::config::PageServerConf;
|
||||
use crate::layered_repository::LayeredRepository;
|
||||
use crate::remote_storage::RemoteIndex;
|
||||
use crate::repository::{Repository, TimelineSyncStatusUpdate};
|
||||
use crate::tenant_config::TenantConf;
|
||||
use crate::thread_mgr;
|
||||
use crate::thread_mgr::ThreadKind;
|
||||
use crate::timelines;
|
||||
@@ -63,7 +64,7 @@ fn access_tenants() -> MutexGuard<'static, HashMap<ZTenantId, Tenant>> {
|
||||
TENANTS.lock().unwrap()
|
||||
}
|
||||
|
||||
// Sets up wal redo manager and repository for tenant. Reduces code duplocation.
|
||||
// Sets up wal redo manager and repository for tenant. Reduces code duplication.
|
||||
// Used during pageserver startup, or when new tenant is attached to pageserver.
|
||||
pub fn load_local_repo(
|
||||
conf: &'static PageServerConf,
|
||||
@@ -76,13 +77,9 @@ pub fn load_local_repo(
|
||||
let walredo_mgr = PostgresRedoManager::new(conf, tenant_id);
|
||||
|
||||
// Try to load config file
|
||||
let tenant_conf = match TenantConf::load(conf, tenant_id) {
|
||||
Ok(tenant_conf) => tenant_conf,
|
||||
Err(e) => {
|
||||
error!("Failed to load tenant state: {:?}", e);
|
||||
TenantConf::from(conf)
|
||||
}
|
||||
};
|
||||
let tenant_conf = LayeredRepository::load_tenantconf(conf, tenant_id)
|
||||
.with_context(|| format!("Failed to load tenant state for id {}", tenant_id))
|
||||
.unwrap();
|
||||
|
||||
// Set up an object repository, for actual data storage.
|
||||
let repo: Arc<LayeredRepository> = Arc::new(LayeredRepository::new(
|
||||
|
||||
@@ -20,10 +20,10 @@ use zenith_utils::{crashsafe_dir, logging};
|
||||
|
||||
use crate::{
|
||||
config::PageServerConf,
|
||||
config::TenantConf,
|
||||
layered_repository::metadata::TimelineMetadata,
|
||||
remote_storage::RemoteIndex,
|
||||
repository::{LocalTimelineState, Repository},
|
||||
tenant_config::TenantConf,
|
||||
DatadirTimeline, RepositoryImpl,
|
||||
};
|
||||
use crate::{import_datadir, LOG_FILE_NAME};
|
||||
@@ -211,6 +211,9 @@ pub fn create_repo(
|
||||
crashsafe_dir::create_dir(conf.timelines_path(&tenant_id))?;
|
||||
info!("created directory structure in {}", repo_dir.display());
|
||||
|
||||
// Save tenant's config
|
||||
LayeredRepository::save_tenantconf(conf, tenant_id, tenant_conf, true)?;
|
||||
|
||||
Ok(Arc::new(LayeredRepository::new(
|
||||
conf,
|
||||
tenant_conf,
|
||||
|
||||
Reference in New Issue
Block a user