LCOV - code coverage report
Current view: top level - pageserver/src - config.rs (source / functions) Coverage Total Hit
Test: 1b0a6a0c05cee5a7de360813c8034804e105ce1c.info Lines: 84.4 % 289 244
Test Date: 2025-03-12 00:01:28 Functions: 53.7 % 41 22

            Line data    Source code
       1              : //! Functions for handling page server configuration options
       2              : //!
       3              : //! Configuration options can be set in the pageserver.toml configuration
       4              : //! file, or on the command line.
       5              : //! See also `settings.md` for better description on every parameter.
       6              : 
       7              : use std::env;
       8              : use std::num::NonZeroUsize;
       9              : use std::sync::Arc;
      10              : use std::time::Duration;
      11              : 
      12              : use anyhow::{Context, bail, ensure};
      13              : use camino::{Utf8Path, Utf8PathBuf};
      14              : use once_cell::sync::OnceCell;
      15              : use pageserver_api::config::{DiskUsageEvictionTaskConfig, MaxVectoredReadBytes};
      16              : use pageserver_api::models::ImageCompressionAlgorithm;
      17              : use pageserver_api::shard::TenantShardId;
      18              : use postgres_backend::AuthType;
      19              : use remote_storage::{RemotePath, RemoteStorageConfig};
      20              : use reqwest::Url;
      21              : use storage_broker::Uri;
      22              : use utils::id::{NodeId, TimelineId};
      23              : use utils::logging::{LogFormat, SecretString};
      24              : use utils::postgres_client::PostgresClientProtocol;
      25              : 
      26              : use crate::tenant::storage_layer::inmemory_layer::IndexEntry;
      27              : use crate::tenant::{TENANTS_SEGMENT_NAME, TIMELINES_SEGMENT_NAME};
      28              : use crate::virtual_file::io_engine;
      29              : use crate::{TENANT_HEATMAP_BASENAME, TENANT_LOCATION_CONFIG_NAME, virtual_file};
      30              : 
      31              : /// Global state of pageserver.
      32              : ///
      33              : /// It's mostly immutable configuration, but some semaphores and the
      34              : /// like crept in over time and the name stuck.
      35              : ///
      36              : /// Instantiated by deserializing `pageserver.toml` into  [`pageserver_api::config::ConfigToml`]
      37              : /// and passing that to [`PageServerConf::parse_and_validate`].
      38              : ///
      39              : /// # Adding a New Field
      40              : ///
      41              : /// 1. Add the field to `pageserver_api::config::ConfigToml`.
      42              : /// 2. Fix compiler errors (exhaustive destructuring will guide you).
      43              : ///
      44              : /// For fields that require additional validation or filling in of defaults at runtime,
      45              : /// check for examples in the [`PageServerConf::parse_and_validate`] method.
      46              : #[derive(Debug, Clone, PartialEq, Eq)]
      47              : pub struct PageServerConf {
      48              :     // Identifier of that particular pageserver so e g safekeepers
      49              :     // can safely distinguish different pageservers
      50              :     pub id: NodeId,
      51              : 
      52              :     /// Example (default): 127.0.0.1:64000
      53              :     pub listen_pg_addr: String,
      54              :     /// Example (default): 127.0.0.1:9898
      55              :     pub listen_http_addr: String,
      56              :     /// Example: 127.0.0.1:9899
      57              :     pub listen_https_addr: Option<String>,
      58              : 
      59              :     pub ssl_key_file: Utf8PathBuf,
      60              :     pub ssl_cert_file: Utf8PathBuf,
      61              : 
      62              :     /// Current availability zone. Used for traffic metrics.
      63              :     pub availability_zone: Option<String>,
      64              : 
      65              :     // Timeout when waiting for WAL receiver to catch up to an LSN given in a GetPage@LSN call.
      66              :     pub wait_lsn_timeout: Duration,
      67              :     // How long to wait for WAL redo to complete.
      68              :     pub wal_redo_timeout: Duration,
      69              : 
      70              :     pub superuser: String,
      71              :     pub locale: String,
      72              : 
      73              :     pub page_cache_size: usize,
      74              :     pub max_file_descriptors: usize,
      75              : 
      76              :     // Repository directory, relative to current working directory.
      77              :     // Normally, the page server changes the current working directory
      78              :     // to the repository, and 'workdir' is always '.'. But we don't do
      79              :     // that during unit testing, because the current directory is global
      80              :     // to the process but different unit tests work on different
      81              :     // repositories.
      82              :     pub workdir: Utf8PathBuf,
      83              : 
      84              :     pub pg_distrib_dir: Utf8PathBuf,
      85              : 
      86              :     // Authentication
      87              :     /// authentication method for the HTTP mgmt API
      88              :     pub http_auth_type: AuthType,
      89              :     /// authentication method for libpq connections from compute
      90              :     pub pg_auth_type: AuthType,
      91              :     /// Path to a file or directory containing public key(s) for verifying JWT tokens.
      92              :     /// Used for both mgmt and compute auth, if enabled.
      93              :     pub auth_validation_public_key_path: Option<Utf8PathBuf>,
      94              : 
      95              :     pub remote_storage_config: Option<RemoteStorageConfig>,
      96              : 
      97              :     pub default_tenant_conf: crate::tenant::config::TenantConf,
      98              : 
      99              :     /// Storage broker endpoints to connect to.
     100              :     pub broker_endpoint: Uri,
     101              :     pub broker_keepalive_interval: Duration,
     102              : 
     103              :     pub log_format: LogFormat,
     104              : 
     105              :     /// Number of tenants which will be concurrently loaded from remote storage proactively on startup or attach.
     106              :     ///
     107              :     /// A lower value implicitly deprioritizes loading such tenants, vs. other work in the system.
     108              :     pub concurrent_tenant_warmup: ConfigurableSemaphore,
     109              : 
     110              :     /// Number of concurrent [`Tenant::gather_size_inputs`](crate::tenant::Tenant::gather_size_inputs) allowed.
     111              :     pub concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore,
     112              :     /// Limit of concurrent [`Tenant::gather_size_inputs`] issued by module `eviction_task`.
     113              :     /// The number of permits is the same as `concurrent_tenant_size_logical_size_queries`.
     114              :     /// See the comment in `eviction_task` for details.
     115              :     ///
     116              :     /// [`Tenant::gather_size_inputs`]: crate::tenant::Tenant::gather_size_inputs
     117              :     pub eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore,
     118              : 
     119              :     // How often to collect metrics and send them to the metrics endpoint.
     120              :     pub metric_collection_interval: Duration,
     121              :     // How often to send unchanged cached metrics to the metrics endpoint.
     122              :     pub metric_collection_endpoint: Option<Url>,
     123              :     pub metric_collection_bucket: Option<RemoteStorageConfig>,
     124              :     pub synthetic_size_calculation_interval: Duration,
     125              : 
     126              :     pub disk_usage_based_eviction: Option<DiskUsageEvictionTaskConfig>,
     127              : 
     128              :     pub test_remote_failures: u64,
     129              : 
     130              :     pub ondemand_download_behavior_treat_error_as_warn: bool,
     131              : 
     132              :     /// How long will background tasks be delayed at most after initial load of tenants.
     133              :     ///
     134              :     /// Our largest initialization completions are in the range of 100-200s, so perhaps 10s works
     135              :     /// as we now isolate initial loading, initial logical size calculation and background tasks.
     136              :     /// Smaller nodes will have background tasks "not running" for this long unless every timeline
     137              :     /// has it's initial logical size calculated. Not running background tasks for some seconds is
     138              :     /// not terrible.
     139              :     pub background_task_maximum_delay: Duration,
     140              : 
     141              :     pub control_plane_api: Option<Url>,
     142              : 
     143              :     /// JWT token for use with the control plane API.
     144              :     pub control_plane_api_token: Option<SecretString>,
     145              : 
     146              :     pub import_pgdata_upcall_api: Option<Url>,
     147              :     pub import_pgdata_upcall_api_token: Option<SecretString>,
     148              :     pub import_pgdata_aws_endpoint_url: Option<Url>,
     149              : 
     150              :     /// If true, pageserver will make best-effort to operate without a control plane: only
     151              :     /// for use in major incidents.
     152              :     pub control_plane_emergency_mode: bool,
     153              : 
     154              :     /// How many heatmap uploads may be done concurrency: lower values implicitly deprioritize
     155              :     /// heatmap uploads vs. other remote storage operations.
     156              :     pub heatmap_upload_concurrency: usize,
     157              : 
     158              :     /// How many remote storage downloads may be done for secondary tenants concurrently.  Implicitly
     159              :     /// deprioritises secondary downloads vs. remote storage operations for attached tenants.
     160              :     pub secondary_download_concurrency: usize,
     161              : 
     162              :     /// Maximum number of WAL records to be ingested and committed at the same time
     163              :     pub ingest_batch_size: u64,
     164              : 
     165              :     pub virtual_file_io_engine: virtual_file::IoEngineKind,
     166              : 
     167              :     pub max_vectored_read_bytes: MaxVectoredReadBytes,
     168              : 
     169              :     pub image_compression: ImageCompressionAlgorithm,
     170              : 
     171              :     /// Whether to offload archived timelines automatically
     172              :     pub timeline_offloading: bool,
     173              : 
     174              :     /// How many bytes of ephemeral layer content will we allow per kilobyte of RAM.  When this
     175              :     /// is exceeded, we start proactively closing ephemeral layers to limit the total amount
     176              :     /// of ephemeral data.
     177              :     ///
     178              :     /// Setting this to zero disables limits on total ephemeral layer size.
     179              :     pub ephemeral_bytes_per_memory_kb: usize,
     180              : 
     181              :     pub l0_flush: crate::l0_flush::L0FlushConfig,
     182              : 
     183              :     /// Direct IO settings
     184              :     pub virtual_file_io_mode: virtual_file::IoMode,
     185              : 
     186              :     /// Optionally disable disk syncs (unsafe!)
     187              :     pub no_sync: bool,
     188              : 
     189              :     pub wal_receiver_protocol: PostgresClientProtocol,
     190              : 
     191              :     pub page_service_pipelining: pageserver_api::config::PageServicePipeliningConfig,
     192              : 
     193              :     pub get_vectored_concurrent_io: pageserver_api::config::GetVectoredConcurrentIo,
     194              : 
     195              :     /// Enable read path debugging. If enabled, read key errors will print a backtrace of the layer
     196              :     /// files read.
     197              :     pub enable_read_path_debugging: bool,
     198              : 
     199              :     /// Interpreted protocol feature: if enabled, validate that the logical WAL received from
     200              :     /// safekeepers does not have gaps.
     201              :     pub validate_wal_contiguity: bool,
     202              : 
     203              :     /// When set, the previously written to disk heatmap is loaded on tenant attach and used
     204              :     /// to avoid clobbering the heatmap from new, cold, attached locations.
     205              :     pub load_previous_heatmap: bool,
     206              : 
     207              :     /// When set, include visible layers in the next uploaded heatmaps of an unarchived timeline.
     208              :     pub generate_unarchival_heatmap: bool,
     209              : }
     210              : 
     211              : /// Token for authentication to safekeepers
     212              : ///
     213              : /// We do not want to store this in a PageServerConf because the latter may be logged
     214              : /// and/or serialized at a whim, while the token is secret. Currently this token is the
     215              : /// same for accessing all tenants/timelines, but may become per-tenant/per-timeline in
     216              : /// the future, more tokens and auth may arrive for storage broker, completely changing the logic.
     217              : /// Hence, we resort to a global variable for now instead of passing the token from the
     218              : /// startup code to the connection code through a dozen layers.
     219              : pub static SAFEKEEPER_AUTH_TOKEN: OnceCell<Arc<String>> = OnceCell::new();
     220              : 
     221              : impl PageServerConf {
     222              :     //
     223              :     // Repository paths, relative to workdir.
     224              :     //
     225              : 
     226        15508 :     pub fn tenants_path(&self) -> Utf8PathBuf {
     227        15508 :         self.workdir.join(TENANTS_SEGMENT_NAME)
     228        15508 :     }
     229              : 
     230          144 :     pub fn deletion_prefix(&self) -> Utf8PathBuf {
     231          144 :         self.workdir.join("deletion")
     232          144 :     }
     233              : 
     234            0 :     pub fn metadata_path(&self) -> Utf8PathBuf {
     235            0 :         self.workdir.join("metadata.json")
     236            0 :     }
     237              : 
     238           56 :     pub fn deletion_list_path(&self, sequence: u64) -> Utf8PathBuf {
     239              :         // Encode a version in the filename, so that if we ever switch away from JSON we can
     240              :         // increment this.
     241              :         const VERSION: u8 = 1;
     242              : 
     243           56 :         self.deletion_prefix()
     244           56 :             .join(format!("{sequence:016x}-{VERSION:02x}.list"))
     245           56 :     }
     246              : 
     247           48 :     pub fn deletion_header_path(&self) -> Utf8PathBuf {
     248              :         // Encode a version in the filename, so that if we ever switch away from JSON we can
     249              :         // increment this.
     250              :         const VERSION: u8 = 1;
     251              : 
     252           48 :         self.deletion_prefix().join(format!("header-{VERSION:02x}"))
     253           48 :     }
     254              : 
     255        15404 :     pub fn tenant_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     256        15404 :         self.tenants_path().join(tenant_shard_id.to_string())
     257        15404 :     }
     258              : 
     259              :     /// Points to a place in pageserver's local directory,
     260              :     /// where certain tenant's LocationConf be stored.
     261            0 :     pub(crate) fn tenant_location_config_path(
     262            0 :         &self,
     263            0 :         tenant_shard_id: &TenantShardId,
     264            0 :     ) -> Utf8PathBuf {
     265            0 :         self.tenant_path(tenant_shard_id)
     266            0 :             .join(TENANT_LOCATION_CONFIG_NAME)
     267            0 :     }
     268              : 
     269          452 :     pub(crate) fn tenant_heatmap_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     270          452 :         self.tenant_path(tenant_shard_id)
     271          452 :             .join(TENANT_HEATMAP_BASENAME)
     272          452 :     }
     273              : 
     274        14492 :     pub fn timelines_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     275        14492 :         self.tenant_path(tenant_shard_id)
     276        14492 :             .join(TIMELINES_SEGMENT_NAME)
     277        14492 :     }
     278              : 
     279        13576 :     pub fn timeline_path(
     280        13576 :         &self,
     281        13576 :         tenant_shard_id: &TenantShardId,
     282        13576 :         timeline_id: &TimelineId,
     283        13576 :     ) -> Utf8PathBuf {
     284        13576 :         self.timelines_path(tenant_shard_id)
     285        13576 :             .join(timeline_id.to_string())
     286        13576 :     }
     287              : 
     288              :     /// Turns storage remote path of a file into its local path.
     289            0 :     pub fn local_path(&self, remote_path: &RemotePath) -> Utf8PathBuf {
     290            0 :         remote_path.with_base(&self.workdir)
     291            0 :     }
     292              : 
     293              :     //
     294              :     // Postgres distribution paths
     295              :     //
     296           40 :     pub fn pg_distrib_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
     297           40 :         let path = self.pg_distrib_dir.clone();
     298           40 : 
     299           40 :         #[allow(clippy::manual_range_patterns)]
     300           40 :         match pg_version {
     301           40 :             14 | 15 | 16 | 17 => Ok(path.join(format!("v{pg_version}"))),
     302            0 :             _ => bail!("Unsupported postgres version: {}", pg_version),
     303              :         }
     304           40 :     }
     305              : 
     306           20 :     pub fn pg_bin_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
     307           20 :         Ok(self.pg_distrib_dir(pg_version)?.join("bin"))
     308           20 :     }
     309           20 :     pub fn pg_lib_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
     310           20 :         Ok(self.pg_distrib_dir(pg_version)?.join("lib"))
     311           20 :     }
     312              : 
     313              :     /// Parse a configuration file (pageserver.toml) into a PageServerConf struct,
     314              :     /// validating the input and failing on errors.
     315              :     ///
     316              :     /// This leaves any options not present in the file in the built-in defaults.
     317          488 :     pub fn parse_and_validate(
     318          488 :         id: NodeId,
     319          488 :         config_toml: pageserver_api::config::ConfigToml,
     320          488 :         workdir: &Utf8Path,
     321          488 :     ) -> anyhow::Result<Self> {
     322          488 :         let pageserver_api::config::ConfigToml {
     323          488 :             listen_pg_addr,
     324          488 :             listen_http_addr,
     325          488 :             listen_https_addr,
     326          488 :             ssl_key_file,
     327          488 :             ssl_cert_file,
     328          488 :             availability_zone,
     329          488 :             wait_lsn_timeout,
     330          488 :             wal_redo_timeout,
     331          488 :             superuser,
     332          488 :             locale,
     333          488 :             page_cache_size,
     334          488 :             max_file_descriptors,
     335          488 :             pg_distrib_dir,
     336          488 :             http_auth_type,
     337          488 :             pg_auth_type,
     338          488 :             auth_validation_public_key_path,
     339          488 :             remote_storage,
     340          488 :             broker_endpoint,
     341          488 :             broker_keepalive_interval,
     342          488 :             log_format,
     343          488 :             metric_collection_interval,
     344          488 :             metric_collection_endpoint,
     345          488 :             metric_collection_bucket,
     346          488 :             synthetic_size_calculation_interval,
     347          488 :             disk_usage_based_eviction,
     348          488 :             test_remote_failures,
     349          488 :             ondemand_download_behavior_treat_error_as_warn,
     350          488 :             background_task_maximum_delay,
     351          488 :             control_plane_api,
     352          488 :             control_plane_api_token,
     353          488 :             control_plane_emergency_mode,
     354          488 :             import_pgdata_upcall_api,
     355          488 :             import_pgdata_upcall_api_token,
     356          488 :             import_pgdata_aws_endpoint_url,
     357          488 :             heatmap_upload_concurrency,
     358          488 :             secondary_download_concurrency,
     359          488 :             ingest_batch_size,
     360          488 :             max_vectored_read_bytes,
     361          488 :             image_compression,
     362          488 :             timeline_offloading,
     363          488 :             ephemeral_bytes_per_memory_kb,
     364          488 :             l0_flush,
     365          488 :             virtual_file_io_mode,
     366          488 :             concurrent_tenant_warmup,
     367          488 :             concurrent_tenant_size_logical_size_queries,
     368          488 :             virtual_file_io_engine,
     369          488 :             tenant_config,
     370          488 :             no_sync,
     371          488 :             wal_receiver_protocol,
     372          488 :             page_service_pipelining,
     373          488 :             get_vectored_concurrent_io,
     374          488 :             enable_read_path_debugging,
     375          488 :             validate_wal_contiguity,
     376          488 :             load_previous_heatmap,
     377          488 :             generate_unarchival_heatmap,
     378          488 :         } = config_toml;
     379              : 
     380          488 :         let mut conf = PageServerConf {
     381              :             // ------------------------------------------------------------
     382              :             // fields that are already fully validated by the ConfigToml Deserialize impl
     383              :             // ------------------------------------------------------------
     384          488 :             listen_pg_addr,
     385          488 :             listen_http_addr,
     386          488 :             listen_https_addr,
     387          488 :             ssl_key_file,
     388          488 :             ssl_cert_file,
     389          488 :             availability_zone,
     390          488 :             wait_lsn_timeout,
     391          488 :             wal_redo_timeout,
     392          488 :             superuser,
     393          488 :             locale,
     394          488 :             page_cache_size,
     395          488 :             max_file_descriptors,
     396          488 :             http_auth_type,
     397          488 :             pg_auth_type,
     398          488 :             auth_validation_public_key_path,
     399          488 :             remote_storage_config: remote_storage,
     400          488 :             broker_endpoint,
     401          488 :             broker_keepalive_interval,
     402          488 :             log_format,
     403          488 :             metric_collection_interval,
     404          488 :             metric_collection_endpoint,
     405          488 :             metric_collection_bucket,
     406          488 :             synthetic_size_calculation_interval,
     407          488 :             disk_usage_based_eviction,
     408          488 :             test_remote_failures,
     409          488 :             ondemand_download_behavior_treat_error_as_warn,
     410          488 :             background_task_maximum_delay,
     411          488 :             control_plane_api,
     412          488 :             control_plane_emergency_mode,
     413          488 :             heatmap_upload_concurrency,
     414          488 :             secondary_download_concurrency,
     415          488 :             ingest_batch_size,
     416          488 :             max_vectored_read_bytes,
     417          488 :             image_compression,
     418          488 :             timeline_offloading,
     419          488 :             ephemeral_bytes_per_memory_kb,
     420          488 :             import_pgdata_upcall_api,
     421          488 :             import_pgdata_upcall_api_token: import_pgdata_upcall_api_token.map(SecretString::from),
     422          488 :             import_pgdata_aws_endpoint_url,
     423          488 :             wal_receiver_protocol,
     424          488 :             page_service_pipelining,
     425          488 :             get_vectored_concurrent_io,
     426          488 : 
     427          488 :             // ------------------------------------------------------------
     428          488 :             // fields that require additional validation or custom handling
     429          488 :             // ------------------------------------------------------------
     430          488 :             workdir: workdir.to_owned(),
     431          488 :             pg_distrib_dir: pg_distrib_dir.unwrap_or_else(|| {
     432            4 :                 std::env::current_dir()
     433            4 :                     .expect("current_dir() failed")
     434            4 :                     .try_into()
     435            4 :                     .expect("current_dir() is not a valid Utf8Path")
     436          488 :             }),
     437          488 :             control_plane_api_token: control_plane_api_token.map(SecretString::from),
     438          488 :             id,
     439          488 :             default_tenant_conf: tenant_config,
     440          488 :             concurrent_tenant_warmup: ConfigurableSemaphore::new(concurrent_tenant_warmup),
     441          488 :             concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::new(
     442          488 :                 concurrent_tenant_size_logical_size_queries,
     443          488 :             ),
     444          488 :             eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore::new(
     445          488 :                 // re-use `concurrent_tenant_size_logical_size_queries`
     446          488 :                 concurrent_tenant_size_logical_size_queries,
     447          488 :             ),
     448          488 :             virtual_file_io_engine: match virtual_file_io_engine {
     449            0 :                 Some(v) => v,
     450          488 :                 None => match crate::virtual_file::io_engine_feature_test()
     451          488 :                     .context("auto-detect virtual_file_io_engine")?
     452              :                 {
     453          488 :                     io_engine::FeatureTestResult::PlatformPreferred(v) => v, // make no noise
     454            0 :                     io_engine::FeatureTestResult::Worse { engine, remark } => {
     455            0 :                         // TODO: bubble this up to the caller so we can tracing::warn! it.
     456            0 :                         eprintln!(
     457            0 :                             "auto-detected IO engine is not platform-preferred: engine={engine:?} remark={remark:?}"
     458            0 :                         );
     459            0 :                         engine
     460              :                     }
     461              :                 },
     462              :             },
     463          488 :             l0_flush: l0_flush
     464          488 :                 .map(crate::l0_flush::L0FlushConfig::from)
     465          488 :                 .unwrap_or_default(),
     466          488 :             virtual_file_io_mode: virtual_file_io_mode.unwrap_or(virtual_file::IoMode::preferred()),
     467          488 :             no_sync: no_sync.unwrap_or(false),
     468          488 :             enable_read_path_debugging: enable_read_path_debugging.unwrap_or(false),
     469          488 :             validate_wal_contiguity: validate_wal_contiguity.unwrap_or(false),
     470          488 :             load_previous_heatmap: load_previous_heatmap.unwrap_or(true),
     471          488 :             generate_unarchival_heatmap: generate_unarchival_heatmap.unwrap_or(true),
     472          488 :         };
     473          488 : 
     474          488 :         // ------------------------------------------------------------
     475          488 :         // custom validation code that covers more than one field in isolation
     476          488 :         // ------------------------------------------------------------
     477          488 : 
     478          488 :         if conf.http_auth_type == AuthType::NeonJWT || conf.pg_auth_type == AuthType::NeonJWT {
     479            0 :             let auth_validation_public_key_path = conf
     480            0 :                 .auth_validation_public_key_path
     481            0 :                 .get_or_insert_with(|| workdir.join("auth_public_key.pem"));
     482            0 :             ensure!(
     483            0 :                 auth_validation_public_key_path.exists(),
     484            0 :                 format!(
     485            0 :                     "Can't find auth_validation_public_key at '{auth_validation_public_key_path}'",
     486            0 :                 )
     487              :             );
     488          488 :         }
     489              : 
     490          488 :         IndexEntry::validate_checkpoint_distance(conf.default_tenant_conf.checkpoint_distance)
     491          488 :             .map_err(anyhow::Error::msg)
     492          488 :             .with_context(|| {
     493            0 :                 format!(
     494            0 :                     "effective checkpoint distance is unsupported: {}",
     495            0 :                     conf.default_tenant_conf.checkpoint_distance
     496            0 :                 )
     497          488 :             })?;
     498              : 
     499          488 :         Ok(conf)
     500          488 :     }
     501              : 
     502              :     #[cfg(test)]
     503          488 :     pub fn test_repo_dir(test_name: &str) -> Utf8PathBuf {
     504          488 :         let test_output_dir = std::env::var("TEST_OUTPUT").unwrap_or("../tmp_check".into());
     505          488 : 
     506          488 :         let test_id = uuid::Uuid::new_v4();
     507          488 :         Utf8PathBuf::from(format!("{test_output_dir}/test_{test_name}_{test_id}"))
     508          488 :     }
     509              : 
     510          484 :     pub fn dummy_conf(repo_dir: Utf8PathBuf) -> Self {
     511          484 :         let pg_distrib_dir = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../pg_install");
     512          484 : 
     513          484 :         let config_toml = pageserver_api::config::ConfigToml {
     514          484 :             wait_lsn_timeout: Duration::from_secs(60),
     515          484 :             wal_redo_timeout: Duration::from_secs(60),
     516          484 :             pg_distrib_dir: Some(pg_distrib_dir),
     517          484 :             metric_collection_interval: Duration::from_secs(60),
     518          484 :             synthetic_size_calculation_interval: Duration::from_secs(60),
     519          484 :             background_task_maximum_delay: Duration::ZERO,
     520          484 :             load_previous_heatmap: Some(true),
     521          484 :             generate_unarchival_heatmap: Some(true),
     522          484 :             ..Default::default()
     523          484 :         };
     524          484 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &repo_dir).unwrap()
     525          484 :     }
     526              : }
     527              : 
     528            0 : #[derive(serde::Deserialize, serde::Serialize)]
     529              : #[serde(deny_unknown_fields)]
     530              : pub struct PageserverIdentity {
     531              :     pub id: NodeId,
     532              : }
     533              : 
     534              : /// Configurable semaphore permits setting.
     535              : ///
     536              : /// Does not allow semaphore permits to be zero, because at runtime initially zero permits and empty
     537              : /// semaphore cannot be distinguished, leading any feature using these to await forever (or until
     538              : /// new permits are added).
     539              : #[derive(Debug, Clone)]
     540              : pub struct ConfigurableSemaphore {
     541              :     initial_permits: NonZeroUsize,
     542              :     inner: std::sync::Arc<tokio::sync::Semaphore>,
     543              : }
     544              : 
     545              : impl ConfigurableSemaphore {
     546              :     /// Initializse using a non-zero amount of permits.
     547              :     ///
     548              :     /// Require a non-zero initial permits, because using permits == 0 is a crude way to disable a
     549              :     /// feature such as [`Tenant::gather_size_inputs`]. Otherwise any semaphore using future will
     550              :     /// behave like [`futures::future::pending`], just waiting until new permits are added.
     551              :     ///
     552              :     /// [`Tenant::gather_size_inputs`]: crate::tenant::Tenant::gather_size_inputs
     553         1464 :     pub fn new(initial_permits: NonZeroUsize) -> Self {
     554         1464 :         ConfigurableSemaphore {
     555         1464 :             initial_permits,
     556         1464 :             inner: std::sync::Arc::new(tokio::sync::Semaphore::new(initial_permits.get())),
     557         1464 :         }
     558         1464 :     }
     559              : 
     560              :     /// Returns the configured amount of permits.
     561            0 :     pub fn initial_permits(&self) -> NonZeroUsize {
     562            0 :         self.initial_permits
     563            0 :     }
     564              : }
     565              : 
     566              : impl PartialEq for ConfigurableSemaphore {
     567            0 :     fn eq(&self, other: &Self) -> bool {
     568            0 :         // the number of permits can be increased at runtime, so we cannot really fulfill the
     569            0 :         // PartialEq value equality otherwise
     570            0 :         self.initial_permits == other.initial_permits
     571            0 :     }
     572              : }
     573              : 
     574              : impl Eq for ConfigurableSemaphore {}
     575              : 
     576              : impl ConfigurableSemaphore {
     577            0 :     pub fn inner(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
     578            0 :         &self.inner
     579            0 :     }
     580              : }
     581              : 
     582              : #[cfg(test)]
     583              : mod tests {
     584              : 
     585              :     use camino::Utf8PathBuf;
     586              :     use utils::id::NodeId;
     587              : 
     588              :     use super::PageServerConf;
     589              : 
     590              :     #[test]
     591            4 :     fn test_empty_config_toml_is_valid() {
     592            4 :         // we use Default impl of everything in this situation
     593            4 :         let input = r#"
     594            4 :         "#;
     595            4 :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     596            4 :             .expect("empty config is valid");
     597            4 :         let workdir = Utf8PathBuf::from("/nonexistent");
     598            4 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir)
     599            4 :             .expect("parse_and_validate");
     600            4 :     }
     601              : 
     602              :     /// If there's a typo in the pageserver config, we'd rather catch that typo
     603              :     /// and fail pageserver startup than silently ignoring the typo, leaving whoever
     604              :     /// made it in the believe that their config change is effective.
     605              :     ///
     606              :     /// The default in serde is to allow unknown fields, so, we rely
     607              :     /// on developer+review discipline to add `deny_unknown_fields` when adding
     608              :     /// new structs to the config, and these tests here as a regression test.
     609              :     ///
     610              :     /// The alternative to all of this would be to allow unknown fields in the config.
     611              :     /// To catch them, we could have a config check tool or mgmt API endpoint that
     612              :     /// compares the effective config with the TOML on disk and makes sure that
     613              :     /// the on-disk TOML is a strict subset of the effective config.
     614              :     mod unknown_fields_handling {
     615              :         macro_rules! test {
     616              :             ($short_name:ident, $input:expr) => {
     617              :                 #[test]
     618           20 :                 fn $short_name() {
     619           20 :                     let input = $input;
     620           20 :                     let err = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(&input)
     621           20 :                         .expect_err("some_invalid_field is an invalid field");
     622           20 :                     dbg!(&err);
     623           20 :                     assert!(err.to_string().contains("some_invalid_field"));
     624           20 :                 }
     625              :             };
     626              :         }
     627              :         use indoc::indoc;
     628              : 
     629              :         test!(
     630              :             toplevel,
     631              :             indoc! {r#"
     632              :                 some_invalid_field = 23
     633              :             "#}
     634              :         );
     635              : 
     636              :         test!(
     637              :             toplevel_nested,
     638              :             indoc! {r#"
     639              :                 [some_invalid_field]
     640              :                 foo = 23
     641              :             "#}
     642              :         );
     643              : 
     644              :         test!(
     645              :             disk_usage_based_eviction,
     646              :             indoc! {r#"
     647              :                 [disk_usage_based_eviction]
     648              :                 some_invalid_field = 23
     649              :             "#}
     650              :         );
     651              : 
     652              :         test!(
     653              :             tenant_config,
     654              :             indoc! {r#"
     655              :                 [tenant_config]
     656              :                 some_invalid_field = 23
     657              :             "#}
     658              :         );
     659              : 
     660              :         test!(
     661              :             l0_flush,
     662              :             indoc! {r#"
     663              :                 [l0_flush]
     664              :                 mode = "direct"
     665              :                 some_invalid_field = 23
     666              :             "#}
     667              :         );
     668              : 
     669              :         // TODO: fix this => https://github.com/neondatabase/neon/issues/8915
     670              :         // test!(
     671              :         //     remote_storage_config,
     672              :         //     indoc! {r#"
     673              :         //         [remote_storage_config]
     674              :         //         local_path = "/nonexistent"
     675              :         //         some_invalid_field = 23
     676              :         //     "#}
     677              :         // );
     678              :     }
     679              : }
        

Generated by: LCOV version 2.1-beta