LCOV - code coverage report
Current view: top level - pageserver/src - config.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 84.4 % 372 314
Test Date: 2025-07-16 12:29:03 Functions: 52.6 % 38 20

            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              : pub mod ignored_fields;
       8              : 
       9              : use std::env;
      10              : use std::num::NonZeroUsize;
      11              : use std::sync::Arc;
      12              : use std::time::Duration;
      13              : 
      14              : use anyhow::{Context, ensure};
      15              : use camino::{Utf8Path, Utf8PathBuf};
      16              : use once_cell::sync::OnceCell;
      17              : use pageserver_api::config::{
      18              :     DiskUsageEvictionTaskConfig, MaxGetVectoredKeys, MaxVectoredReadBytes,
      19              :     PageServicePipeliningConfig, PageServicePipeliningConfigPipelined, PostHogConfig,
      20              : };
      21              : use pageserver_api::models::ImageCompressionAlgorithm;
      22              : use pageserver_api::shard::TenantShardId;
      23              : use pem::Pem;
      24              : use postgres_backend::AuthType;
      25              : use postgres_ffi::PgMajorVersion;
      26              : use remote_storage::{RemotePath, RemoteStorageConfig};
      27              : use reqwest::Url;
      28              : use storage_broker::Uri;
      29              : use utils::id::{NodeId, TimelineId};
      30              : use utils::logging::{LogFormat, SecretString};
      31              : 
      32              : use crate::tenant::storage_layer::inmemory_layer::IndexEntry;
      33              : use crate::tenant::{TENANTS_SEGMENT_NAME, TIMELINES_SEGMENT_NAME};
      34              : use crate::virtual_file::io_engine;
      35              : use crate::{TENANT_HEATMAP_BASENAME, TENANT_LOCATION_CONFIG_NAME, virtual_file};
      36              : 
      37              : /// Global state of pageserver.
      38              : ///
      39              : /// It's mostly immutable configuration, but some semaphores and the
      40              : /// like crept in over time and the name stuck.
      41              : ///
      42              : /// Instantiated by deserializing `pageserver.toml` into  [`pageserver_api::config::ConfigToml`]
      43              : /// and passing that to [`PageServerConf::parse_and_validate`].
      44              : ///
      45              : /// # Adding a New Field
      46              : ///
      47              : /// 1. Add the field to `pageserver_api::config::ConfigToml`.
      48              : /// 2. Fix compiler errors (exhaustive destructuring will guide you).
      49              : ///
      50              : /// For fields that require additional validation or filling in of defaults at runtime,
      51              : /// check for examples in the [`PageServerConf::parse_and_validate`] method.
      52              : #[derive(Debug, Clone)]
      53              : pub struct PageServerConf {
      54              :     // Identifier of that particular pageserver so e g safekeepers
      55              :     // can safely distinguish different pageservers
      56              :     pub id: NodeId,
      57              : 
      58              :     /// Example (default): 127.0.0.1:64000
      59              :     pub listen_pg_addr: String,
      60              :     /// Example (default): 127.0.0.1:9898
      61              :     pub listen_http_addr: String,
      62              :     /// Example: 127.0.0.1:9899
      63              :     pub listen_https_addr: Option<String>,
      64              :     /// If set, expose a gRPC API on this address.
      65              :     /// Example: 127.0.0.1:51051
      66              :     ///
      67              :     /// EXPERIMENTAL: this protocol is unstable and under active development.
      68              :     pub listen_grpc_addr: Option<String>,
      69              : 
      70              :     /// Path to a file with certificate's private key for https and gRPC API.
      71              :     /// Default: server.key
      72              :     pub ssl_key_file: Utf8PathBuf,
      73              :     /// Path to a file with a X509 certificate for https and gRPC API.
      74              :     /// Default: server.crt
      75              :     pub ssl_cert_file: Utf8PathBuf,
      76              :     /// Period to reload certificate and private key from files.
      77              :     /// Default: 60s.
      78              :     pub ssl_cert_reload_period: Duration,
      79              :     /// Trusted root CA certificates to use in https APIs in PEM format.
      80              :     pub ssl_ca_certs: Vec<Pem>,
      81              : 
      82              :     /// Current availability zone. Used for traffic metrics.
      83              :     pub availability_zone: Option<String>,
      84              : 
      85              :     // Timeout when waiting for WAL receiver to catch up to an LSN given in a GetPage@LSN call.
      86              :     pub wait_lsn_timeout: Duration,
      87              :     // How long to wait for WAL redo to complete.
      88              :     pub wal_redo_timeout: Duration,
      89              : 
      90              :     pub superuser: String,
      91              :     pub locale: String,
      92              : 
      93              :     pub page_cache_size: usize,
      94              :     pub max_file_descriptors: usize,
      95              : 
      96              :     // Repository directory, relative to current working directory.
      97              :     // Normally, the page server changes the current working directory
      98              :     // to the repository, and 'workdir' is always '.'. But we don't do
      99              :     // that during unit testing, because the current directory is global
     100              :     // to the process but different unit tests work on different
     101              :     // repositories.
     102              :     pub workdir: Utf8PathBuf,
     103              : 
     104              :     pub pg_distrib_dir: Utf8PathBuf,
     105              : 
     106              :     // Authentication
     107              :     /// authentication method for the HTTP mgmt API
     108              :     pub http_auth_type: AuthType,
     109              :     /// authentication method for libpq connections from compute
     110              :     pub pg_auth_type: AuthType,
     111              :     /// authentication method for gRPC connections from compute
     112              :     pub grpc_auth_type: AuthType,
     113              :     /// Path to a file or directory containing public key(s) for verifying JWT tokens.
     114              :     /// Used for both mgmt and compute auth, if enabled.
     115              :     pub auth_validation_public_key_path: Option<Utf8PathBuf>,
     116              : 
     117              :     pub remote_storage_config: Option<RemoteStorageConfig>,
     118              : 
     119              :     pub default_tenant_conf: pageserver_api::config::TenantConfigToml,
     120              : 
     121              :     /// Storage broker endpoints to connect to.
     122              :     pub broker_endpoint: Uri,
     123              :     pub broker_keepalive_interval: Duration,
     124              : 
     125              :     pub log_format: LogFormat,
     126              : 
     127              :     /// Number of tenants which will be concurrently loaded from remote storage proactively on startup or attach.
     128              :     ///
     129              :     /// A lower value implicitly deprioritizes loading such tenants, vs. other work in the system.
     130              :     pub concurrent_tenant_warmup: ConfigurableSemaphore,
     131              : 
     132              :     /// Number of concurrent [`TenantShard::gather_size_inputs`](crate::tenant::TenantShard::gather_size_inputs) allowed.
     133              :     pub concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore,
     134              :     /// Limit of concurrent [`TenantShard::gather_size_inputs`] issued by module `eviction_task`.
     135              :     /// The number of permits is the same as `concurrent_tenant_size_logical_size_queries`.
     136              :     /// See the comment in `eviction_task` for details.
     137              :     ///
     138              :     /// [`TenantShard::gather_size_inputs`]: crate::tenant::TenantShard::gather_size_inputs
     139              :     pub eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore,
     140              : 
     141              :     // How often to collect metrics and send them to the metrics endpoint.
     142              :     pub metric_collection_interval: Duration,
     143              :     // How often to send unchanged cached metrics to the metrics endpoint.
     144              :     pub metric_collection_endpoint: Option<Url>,
     145              :     pub metric_collection_bucket: Option<RemoteStorageConfig>,
     146              :     pub synthetic_size_calculation_interval: Duration,
     147              : 
     148              :     pub disk_usage_based_eviction: DiskUsageEvictionTaskConfig,
     149              : 
     150              :     // The number of allowed failures in remote storage operations.
     151              :     pub test_remote_failures: u64,
     152              :     // The probability of failure in remote storage operations. Only works when test_remote_failures > 1.
     153              :     // Use 100 for 100% failure, 0 for no failure.
     154              :     pub test_remote_failures_probability: u64,
     155              : 
     156              :     pub ondemand_download_behavior_treat_error_as_warn: bool,
     157              : 
     158              :     /// How long will background tasks be delayed at most after initial load of tenants.
     159              :     ///
     160              :     /// Our largest initialization completions are in the range of 100-200s, so perhaps 10s works
     161              :     /// as we now isolate initial loading, initial logical size calculation and background tasks.
     162              :     /// Smaller nodes will have background tasks "not running" for this long unless every timeline
     163              :     /// has it's initial logical size calculated. Not running background tasks for some seconds is
     164              :     /// not terrible.
     165              :     pub background_task_maximum_delay: Duration,
     166              : 
     167              :     pub control_plane_api: Url,
     168              : 
     169              :     /// JWT token for use with the control plane API.
     170              :     pub control_plane_api_token: Option<SecretString>,
     171              : 
     172              :     pub import_pgdata_upcall_api: Option<Url>,
     173              :     pub import_pgdata_upcall_api_token: Option<SecretString>,
     174              :     pub import_pgdata_aws_endpoint_url: Option<Url>,
     175              : 
     176              :     /// If true, pageserver will make best-effort to operate without a control plane: only
     177              :     /// for use in major incidents.
     178              :     pub control_plane_emergency_mode: bool,
     179              : 
     180              :     /// How many heatmap uploads may be done concurrency: lower values implicitly deprioritize
     181              :     /// heatmap uploads vs. other remote storage operations.
     182              :     pub heatmap_upload_concurrency: usize,
     183              : 
     184              :     /// How many remote storage downloads may be done for secondary tenants concurrently.  Implicitly
     185              :     /// deprioritises secondary downloads vs. remote storage operations for attached tenants.
     186              :     pub secondary_download_concurrency: usize,
     187              : 
     188              :     /// Maximum number of WAL records to be ingested and committed at the same time
     189              :     pub ingest_batch_size: u64,
     190              : 
     191              :     pub virtual_file_io_engine: virtual_file::IoEngineKind,
     192              : 
     193              :     pub max_vectored_read_bytes: MaxVectoredReadBytes,
     194              : 
     195              :     /// Maximum number of keys to be read in a single get_vectored call.
     196              :     pub max_get_vectored_keys: MaxGetVectoredKeys,
     197              : 
     198              :     pub image_compression: ImageCompressionAlgorithm,
     199              : 
     200              :     /// Whether to offload archived timelines automatically
     201              :     pub timeline_offloading: bool,
     202              : 
     203              :     /// How many bytes of ephemeral layer content will we allow per kilobyte of RAM.  When this
     204              :     /// is exceeded, we start proactively closing ephemeral layers to limit the total amount
     205              :     /// of ephemeral data.
     206              :     ///
     207              :     /// Setting this to zero disables limits on total ephemeral layer size.
     208              :     pub ephemeral_bytes_per_memory_kb: usize,
     209              : 
     210              :     pub l0_flush: crate::l0_flush::L0FlushConfig,
     211              : 
     212              :     /// Direct IO settings
     213              :     pub virtual_file_io_mode: virtual_file::IoMode,
     214              : 
     215              :     /// Optionally disable disk syncs (unsafe!)
     216              :     pub no_sync: bool,
     217              : 
     218              :     pub page_service_pipelining: pageserver_api::config::PageServicePipeliningConfig,
     219              : 
     220              :     pub get_vectored_concurrent_io: pageserver_api::config::GetVectoredConcurrentIo,
     221              : 
     222              :     /// Enable read path debugging. If enabled, read key errors will print a backtrace of the layer
     223              :     /// files read.
     224              :     pub enable_read_path_debugging: bool,
     225              : 
     226              :     /// Interpreted protocol feature: if enabled, validate that the logical WAL received from
     227              :     /// safekeepers does not have gaps.
     228              :     pub validate_wal_contiguity: bool,
     229              : 
     230              :     /// When set, the previously written to disk heatmap is loaded on tenant attach and used
     231              :     /// to avoid clobbering the heatmap from new, cold, attached locations.
     232              :     pub load_previous_heatmap: bool,
     233              : 
     234              :     /// When set, include visible layers in the next uploaded heatmaps of an unarchived timeline.
     235              :     pub generate_unarchival_heatmap: bool,
     236              : 
     237              :     pub tracing: Option<pageserver_api::config::Tracing>,
     238              : 
     239              :     /// Enable TLS in page service API.
     240              :     /// Does not force TLS: the client negotiates TLS usage during the handshake.
     241              :     /// Uses key and certificate from ssl_key_file/ssl_cert_file.
     242              :     pub enable_tls_page_service_api: bool,
     243              : 
     244              :     /// Run in development mode, which disables certain safety checks
     245              :     /// such as authentication requirements for HTTP and PostgreSQL APIs.
     246              :     /// This is insecure and should only be used in development environments.
     247              :     pub dev_mode: bool,
     248              : 
     249              :     /// PostHog integration config.
     250              :     pub posthog_config: Option<PostHogConfig>,
     251              : 
     252              :     pub timeline_import_config: pageserver_api::config::TimelineImportConfig,
     253              : 
     254              :     pub basebackup_cache_config: Option<pageserver_api::config::BasebackupCacheConfig>,
     255              : 
     256              :     /// Defines what is a big tenant for the purpose of image layer generation.
     257              :     /// See Timeline::should_check_if_image_layers_required
     258              :     pub image_layer_generation_large_timeline_threshold: Option<u64>,
     259              : 
     260              :     /// Controls whether to collect all metrics on each scrape or to return potentially stale
     261              :     /// results.
     262              :     pub force_metric_collection_on_scrape: bool,
     263              : }
     264              : 
     265              : /// Token for authentication to safekeepers
     266              : ///
     267              : /// We do not want to store this in a PageServerConf because the latter may be logged
     268              : /// and/or serialized at a whim, while the token is secret. Currently this token is the
     269              : /// same for accessing all tenants/timelines, but may become per-tenant/per-timeline in
     270              : /// the future, more tokens and auth may arrive for storage broker, completely changing the logic.
     271              : /// Hence, we resort to a global variable for now instead of passing the token from the
     272              : /// startup code to the connection code through a dozen layers.
     273              : pub static SAFEKEEPER_AUTH_TOKEN: OnceCell<Arc<String>> = OnceCell::new();
     274              : 
     275              : impl PageServerConf {
     276              :     //
     277              :     // Repository paths, relative to workdir.
     278              :     //
     279              : 
     280         4041 :     pub fn tenants_path(&self) -> Utf8PathBuf {
     281         4041 :         self.workdir.join(TENANTS_SEGMENT_NAME)
     282         4041 :     }
     283              : 
     284           36 :     pub fn deletion_prefix(&self) -> Utf8PathBuf {
     285           36 :         self.workdir.join("deletion")
     286           36 :     }
     287              : 
     288            0 :     pub fn metadata_path(&self) -> Utf8PathBuf {
     289            0 :         self.workdir.join("metadata.json")
     290            0 :     }
     291              : 
     292            0 :     pub fn basebackup_cache_dir(&self) -> Utf8PathBuf {
     293            0 :         self.workdir.join("basebackup_cache")
     294            0 :     }
     295              : 
     296           14 :     pub fn deletion_list_path(&self, sequence: u64) -> Utf8PathBuf {
     297              :         // Encode a version in the filename, so that if we ever switch away from JSON we can
     298              :         // increment this.
     299              :         const VERSION: u8 = 1;
     300              : 
     301           14 :         self.deletion_prefix()
     302           14 :             .join(format!("{sequence:016x}-{VERSION:02x}.list"))
     303           14 :     }
     304              : 
     305           12 :     pub fn deletion_header_path(&self) -> Utf8PathBuf {
     306              :         // Encode a version in the filename, so that if we ever switch away from JSON we can
     307              :         // increment this.
     308              :         const VERSION: u8 = 1;
     309              : 
     310           12 :         self.deletion_prefix().join(format!("header-{VERSION:02x}"))
     311           12 :     }
     312              : 
     313         4014 :     pub fn tenant_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     314         4014 :         self.tenants_path().join(tenant_shard_id.to_string())
     315         4014 :     }
     316              : 
     317              :     /// Points to a place in pageserver's local directory,
     318              :     /// where certain tenant's LocationConf be stored.
     319            0 :     pub(crate) fn tenant_location_config_path(
     320            0 :         &self,
     321            0 :         tenant_shard_id: &TenantShardId,
     322            0 :     ) -> Utf8PathBuf {
     323            0 :         self.tenant_path(tenant_shard_id)
     324            0 :             .join(TENANT_LOCATION_CONFIG_NAME)
     325            0 :     }
     326              : 
     327          119 :     pub(crate) fn tenant_heatmap_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     328          119 :         self.tenant_path(tenant_shard_id)
     329          119 :             .join(TENANT_HEATMAP_BASENAME)
     330          119 :     }
     331              : 
     332         3773 :     pub fn timelines_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
     333         3773 :         self.tenant_path(tenant_shard_id)
     334         3773 :             .join(TIMELINES_SEGMENT_NAME)
     335         3773 :     }
     336              : 
     337         3532 :     pub fn timeline_path(
     338         3532 :         &self,
     339         3532 :         tenant_shard_id: &TenantShardId,
     340         3532 :         timeline_id: &TimelineId,
     341         3532 :     ) -> Utf8PathBuf {
     342         3532 :         self.timelines_path(tenant_shard_id)
     343         3532 :             .join(timeline_id.to_string())
     344         3532 :     }
     345              : 
     346              :     /// Turns storage remote path of a file into its local path.
     347            0 :     pub fn local_path(&self, remote_path: &RemotePath) -> Utf8PathBuf {
     348            0 :         remote_path.with_base(&self.workdir)
     349            0 :     }
     350              : 
     351              :     //
     352              :     // Postgres distribution paths
     353              :     //
     354           12 :     pub fn pg_distrib_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<Utf8PathBuf> {
     355           12 :         let path = self.pg_distrib_dir.clone();
     356              : 
     357           12 :         Ok(path.join(pg_version.v_str()))
     358           12 :     }
     359              : 
     360            6 :     pub fn pg_bin_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<Utf8PathBuf> {
     361            6 :         Ok(self.pg_distrib_dir(pg_version)?.join("bin"))
     362            6 :     }
     363            6 :     pub fn pg_lib_dir(&self, pg_version: PgMajorVersion) -> anyhow::Result<Utf8PathBuf> {
     364            6 :         Ok(self.pg_distrib_dir(pg_version)?.join("lib"))
     365            6 :     }
     366              : 
     367              :     /// Parse a configuration file (pageserver.toml) into a PageServerConf struct,
     368              :     /// validating the input and failing on errors.
     369              :     ///
     370              :     /// This leaves any options not present in the file in the built-in defaults.
     371          138 :     pub fn parse_and_validate(
     372          138 :         id: NodeId,
     373          138 :         config_toml: pageserver_api::config::ConfigToml,
     374          138 :         workdir: &Utf8Path,
     375          138 :     ) -> anyhow::Result<Self> {
     376              :         let pageserver_api::config::ConfigToml {
     377          138 :             listen_pg_addr,
     378          138 :             listen_http_addr,
     379          138 :             listen_https_addr,
     380          138 :             listen_grpc_addr,
     381          138 :             ssl_key_file,
     382          138 :             ssl_cert_file,
     383          138 :             ssl_cert_reload_period,
     384          138 :             ssl_ca_file,
     385          138 :             availability_zone,
     386          138 :             wait_lsn_timeout,
     387          138 :             wal_redo_timeout,
     388          138 :             superuser,
     389          138 :             locale,
     390          138 :             page_cache_size,
     391          138 :             max_file_descriptors,
     392          138 :             pg_distrib_dir,
     393          138 :             http_auth_type,
     394          138 :             pg_auth_type,
     395          138 :             grpc_auth_type,
     396          138 :             auth_validation_public_key_path,
     397          138 :             remote_storage,
     398          138 :             broker_endpoint,
     399          138 :             broker_keepalive_interval,
     400          138 :             log_format,
     401          138 :             metric_collection_interval,
     402          138 :             metric_collection_endpoint,
     403          138 :             metric_collection_bucket,
     404          138 :             synthetic_size_calculation_interval,
     405          138 :             disk_usage_based_eviction,
     406          138 :             test_remote_failures,
     407          138 :             test_remote_failures_probability,
     408          138 :             ondemand_download_behavior_treat_error_as_warn,
     409          138 :             background_task_maximum_delay,
     410          138 :             control_plane_api,
     411          138 :             control_plane_api_token,
     412          138 :             control_plane_emergency_mode,
     413          138 :             import_pgdata_upcall_api,
     414          138 :             import_pgdata_upcall_api_token,
     415          138 :             import_pgdata_aws_endpoint_url,
     416          138 :             heatmap_upload_concurrency,
     417          138 :             secondary_download_concurrency,
     418          138 :             ingest_batch_size,
     419          138 :             max_vectored_read_bytes,
     420          138 :             max_get_vectored_keys,
     421          138 :             image_compression,
     422          138 :             timeline_offloading,
     423          138 :             ephemeral_bytes_per_memory_kb,
     424          138 :             l0_flush,
     425          138 :             virtual_file_io_mode,
     426          138 :             concurrent_tenant_warmup,
     427          138 :             concurrent_tenant_size_logical_size_queries,
     428          138 :             virtual_file_io_engine,
     429          138 :             tenant_config,
     430          138 :             no_sync,
     431          138 :             page_service_pipelining,
     432          138 :             get_vectored_concurrent_io,
     433          138 :             enable_read_path_debugging,
     434          138 :             validate_wal_contiguity,
     435          138 :             load_previous_heatmap,
     436          138 :             generate_unarchival_heatmap,
     437          138 :             tracing,
     438          138 :             enable_tls_page_service_api,
     439          138 :             dev_mode,
     440          138 :             posthog_config,
     441          138 :             timeline_import_config,
     442          138 :             basebackup_cache_config,
     443          138 :             image_layer_generation_large_timeline_threshold,
     444          138 :             force_metric_collection_on_scrape,
     445          138 :         } = config_toml;
     446              : 
     447          138 :         let mut conf = PageServerConf {
     448              :             // ------------------------------------------------------------
     449              :             // fields that are already fully validated by the ConfigToml Deserialize impl
     450              :             // ------------------------------------------------------------
     451          138 :             listen_pg_addr,
     452          138 :             listen_http_addr,
     453          138 :             listen_https_addr,
     454          138 :             listen_grpc_addr,
     455          138 :             ssl_key_file,
     456          138 :             ssl_cert_file,
     457          138 :             ssl_cert_reload_period,
     458          138 :             availability_zone,
     459          138 :             wait_lsn_timeout,
     460          138 :             wal_redo_timeout,
     461          138 :             superuser,
     462          138 :             locale,
     463          138 :             page_cache_size,
     464          138 :             max_file_descriptors,
     465          138 :             http_auth_type,
     466          138 :             pg_auth_type,
     467          138 :             grpc_auth_type,
     468          138 :             auth_validation_public_key_path,
     469          138 :             remote_storage_config: remote_storage,
     470          138 :             broker_endpoint,
     471          138 :             broker_keepalive_interval,
     472          138 :             log_format,
     473          138 :             metric_collection_interval,
     474          138 :             metric_collection_endpoint,
     475          138 :             metric_collection_bucket,
     476          138 :             synthetic_size_calculation_interval,
     477          138 :             disk_usage_based_eviction,
     478          138 :             test_remote_failures,
     479          138 :             test_remote_failures_probability,
     480          138 :             ondemand_download_behavior_treat_error_as_warn,
     481          138 :             background_task_maximum_delay,
     482          138 :             control_plane_api: control_plane_api
     483          138 :                 .ok_or_else(|| anyhow::anyhow!("`control_plane_api` must be set"))?,
     484          138 :             control_plane_emergency_mode,
     485          138 :             heatmap_upload_concurrency,
     486          138 :             secondary_download_concurrency,
     487          138 :             ingest_batch_size,
     488          138 :             max_vectored_read_bytes,
     489          138 :             max_get_vectored_keys,
     490          138 :             image_compression,
     491          138 :             timeline_offloading,
     492          138 :             ephemeral_bytes_per_memory_kb,
     493          138 :             import_pgdata_upcall_api,
     494          138 :             import_pgdata_upcall_api_token: import_pgdata_upcall_api_token.map(SecretString::from),
     495          138 :             import_pgdata_aws_endpoint_url,
     496          138 :             page_service_pipelining,
     497          138 :             get_vectored_concurrent_io,
     498          138 :             tracing,
     499          138 :             enable_tls_page_service_api,
     500          138 :             dev_mode,
     501          138 :             timeline_import_config,
     502          138 :             basebackup_cache_config,
     503          138 :             image_layer_generation_large_timeline_threshold,
     504          138 :             force_metric_collection_on_scrape,
     505              : 
     506              :             // ------------------------------------------------------------
     507              :             // fields that require additional validation or custom handling
     508              :             // ------------------------------------------------------------
     509          138 :             workdir: workdir.to_owned(),
     510          138 :             pg_distrib_dir: pg_distrib_dir.unwrap_or_else(|| {
     511           11 :                 std::env::current_dir()
     512           11 :                     .expect("current_dir() failed")
     513           11 :                     .try_into()
     514           11 :                     .expect("current_dir() is not a valid Utf8Path")
     515           11 :             }),
     516          138 :             control_plane_api_token: control_plane_api_token.map(SecretString::from),
     517          138 :             id,
     518          138 :             default_tenant_conf: tenant_config,
     519          138 :             concurrent_tenant_warmup: ConfigurableSemaphore::new(concurrent_tenant_warmup),
     520          138 :             concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::new(
     521          138 :                 concurrent_tenant_size_logical_size_queries,
     522              :             ),
     523          138 :             eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore::new(
     524              :                 // re-use `concurrent_tenant_size_logical_size_queries`
     525          138 :                 concurrent_tenant_size_logical_size_queries,
     526              :             ),
     527          138 :             virtual_file_io_engine: match virtual_file_io_engine {
     528            0 :                 Some(v) => v,
     529          138 :                 None => match crate::virtual_file::io_engine_feature_test()
     530          138 :                     .context("auto-detect virtual_file_io_engine")?
     531              :                 {
     532          138 :                     io_engine::FeatureTestResult::PlatformPreferred(v) => v, // make no noise
     533            0 :                     io_engine::FeatureTestResult::Worse { engine, remark } => {
     534              :                         // TODO: bubble this up to the caller so we can tracing::warn! it.
     535            0 :                         eprintln!(
     536            0 :                             "auto-detected IO engine is not platform-preferred: engine={engine:?} remark={remark:?}"
     537              :                         );
     538            0 :                         engine
     539              :                     }
     540              :                 },
     541              :             },
     542          138 :             l0_flush: l0_flush
     543          138 :                 .map(crate::l0_flush::L0FlushConfig::from)
     544          138 :                 .unwrap_or_default(),
     545          138 :             virtual_file_io_mode: virtual_file_io_mode.unwrap_or(virtual_file::IoMode::preferred()),
     546          138 :             no_sync: no_sync.unwrap_or(false),
     547          138 :             enable_read_path_debugging: enable_read_path_debugging.unwrap_or(false),
     548          138 :             validate_wal_contiguity: validate_wal_contiguity.unwrap_or(false),
     549          138 :             load_previous_heatmap: load_previous_heatmap.unwrap_or(true),
     550          138 :             generate_unarchival_heatmap: generate_unarchival_heatmap.unwrap_or(true),
     551          138 :             ssl_ca_certs: match ssl_ca_file {
     552            0 :                 Some(ssl_ca_file) => {
     553            0 :                     let buf = std::fs::read(ssl_ca_file)?;
     554            0 :                     pem::parse_many(&buf)?
     555            0 :                         .into_iter()
     556            0 :                         .filter(|pem| pem.tag() == "CERTIFICATE")
     557            0 :                         .collect()
     558              :                 }
     559          138 :                 None => Vec::new(),
     560              :             },
     561          138 :             posthog_config,
     562              :         };
     563              : 
     564              :         // ------------------------------------------------------------
     565              :         // custom validation code that covers more than one field in isolation
     566              :         // ------------------------------------------------------------
     567              : 
     568          138 :         if [conf.http_auth_type, conf.pg_auth_type, conf.grpc_auth_type]
     569          138 :             .contains(&AuthType::NeonJWT)
     570              :         {
     571            0 :             let auth_validation_public_key_path = conf
     572            0 :                 .auth_validation_public_key_path
     573            0 :                 .get_or_insert_with(|| workdir.join("auth_public_key.pem"));
     574            0 :             ensure!(
     575            0 :                 auth_validation_public_key_path.exists(),
     576            0 :                 format!(
     577            0 :                     "Can't find auth_validation_public_key at '{auth_validation_public_key_path}'",
     578              :                 )
     579              :             );
     580          138 :         }
     581              : 
     582          138 :         if let Some(tracing_config) = conf.tracing.as_ref() {
     583            1 :             let ratio = &tracing_config.sampling_ratio;
     584            1 :             ensure!(
     585            1 :                 ratio.denominator != 0 && ratio.denominator >= ratio.numerator,
     586            1 :                 format!(
     587            1 :                     "Invalid sampling ratio: {}/{}",
     588              :                     ratio.numerator, ratio.denominator
     589              :                 )
     590              :             );
     591              : 
     592            0 :             let url = Url::parse(&tracing_config.export_config.endpoint)
     593            0 :                 .map_err(anyhow::Error::msg)
     594            0 :                 .with_context(|| {
     595            0 :                     format!(
     596            0 :                         "tracing endpoint URL is invalid : {}",
     597              :                         tracing_config.export_config.endpoint
     598              :                     )
     599            0 :                 })?;
     600              : 
     601            0 :             ensure!(
     602            0 :                 url.scheme() == "http" || url.scheme() == "https",
     603            0 :                 format!(
     604            0 :                     "tracing endpoint URL must start with http:// or https://: {}",
     605              :                     tracing_config.export_config.endpoint
     606              :                 )
     607              :             );
     608          137 :         }
     609              : 
     610          137 :         IndexEntry::validate_checkpoint_distance(conf.default_tenant_conf.checkpoint_distance)
     611          137 :             .map_err(anyhow::Error::msg)
     612          137 :             .with_context(|| {
     613            0 :                 format!(
     614            0 :                     "effective checkpoint distance is unsupported: {}",
     615              :                     conf.default_tenant_conf.checkpoint_distance
     616              :                 )
     617            0 :             })?;
     618              : 
     619              :         if let PageServicePipeliningConfig::Pipelined(PageServicePipeliningConfigPipelined {
     620          137 :             max_batch_size,
     621              :             ..
     622          137 :         }) = conf.page_service_pipelining
     623              :         {
     624          137 :             if max_batch_size.get() > conf.max_get_vectored_keys.get() {
     625            1 :                 return Err(anyhow::anyhow!(
     626            1 :                     "`max_batch_size` ({max_batch_size}) must be less than or equal to `max_get_vectored_keys` ({})",
     627            1 :                     conf.max_get_vectored_keys.get()
     628            1 :                 ));
     629          136 :             }
     630            0 :         };
     631              : 
     632          136 :         Ok(conf)
     633          138 :     }
     634              : 
     635              :     #[cfg(test)]
     636          127 :     pub fn test_repo_dir(test_name: &str) -> Utf8PathBuf {
     637          127 :         let test_output_dir = std::env::var("TEST_OUTPUT").unwrap_or("../tmp_check".into());
     638              : 
     639          127 :         let test_id = uuid::Uuid::new_v4();
     640          127 :         Utf8PathBuf::from(format!("{test_output_dir}/test_{test_name}_{test_id}"))
     641          127 :     }
     642              : 
     643          127 :     pub fn dummy_conf(repo_dir: Utf8PathBuf) -> Self {
     644          127 :         let pg_distrib_dir = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../pg_install");
     645              : 
     646          127 :         let mut config_toml = pageserver_api::config::ConfigToml {
     647          127 :             wait_lsn_timeout: Duration::from_secs(60),
     648          127 :             wal_redo_timeout: Duration::from_secs(60),
     649          127 :             pg_distrib_dir: Some(pg_distrib_dir),
     650          127 :             metric_collection_interval: Duration::from_secs(60),
     651          127 :             synthetic_size_calculation_interval: Duration::from_secs(60),
     652          127 :             background_task_maximum_delay: Duration::ZERO,
     653          127 :             load_previous_heatmap: Some(true),
     654          127 :             generate_unarchival_heatmap: Some(true),
     655          127 :             control_plane_api: Some(Url::parse("http://localhost:6666").unwrap()),
     656          127 :             ..Default::default()
     657          127 :         };
     658              : 
     659              :         // Test authors tend to forget about the default 10min initial lease deadline
     660              :         // when writing tests, which turns their immediate gc requests via mgmt API
     661              :         // into no-ops. Override the binary default here, such that there is no initial
     662              :         // lease deadline by default in tests. Tests that care can always override it
     663              :         // themselves.
     664              :         // Cf https://databricks.atlassian.net/browse/LKB-92?focusedCommentId=6722329
     665          127 :         config_toml.tenant_config.lsn_lease_length = Duration::from_secs(0);
     666              : 
     667          127 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &repo_dir).unwrap()
     668          127 :     }
     669              : }
     670              : 
     671            0 : #[derive(serde::Deserialize, serde::Serialize)]
     672              : pub struct PageserverIdentity {
     673              :     pub id: NodeId,
     674              : }
     675              : 
     676              : /// Configurable semaphore permits setting.
     677              : ///
     678              : /// Does not allow semaphore permits to be zero, because at runtime initially zero permits and empty
     679              : /// semaphore cannot be distinguished, leading any feature using these to await forever (or until
     680              : /// new permits are added).
     681              : #[derive(Debug, Clone)]
     682              : pub struct ConfigurableSemaphore {
     683              :     initial_permits: NonZeroUsize,
     684              :     inner: std::sync::Arc<tokio::sync::Semaphore>,
     685              : }
     686              : 
     687              : impl ConfigurableSemaphore {
     688              :     /// Initializse using a non-zero amount of permits.
     689              :     ///
     690              :     /// Require a non-zero initial permits, because using permits == 0 is a crude way to disable a
     691              :     /// feature such as [`TenantShard::gather_size_inputs`]. Otherwise any semaphore using future will
     692              :     /// behave like [`futures::future::pending`], just waiting until new permits are added.
     693              :     ///
     694              :     /// [`TenantShard::gather_size_inputs`]: crate::tenant::TenantShard::gather_size_inputs
     695          414 :     pub fn new(initial_permits: NonZeroUsize) -> Self {
     696          414 :         ConfigurableSemaphore {
     697          414 :             initial_permits,
     698          414 :             inner: std::sync::Arc::new(tokio::sync::Semaphore::new(initial_permits.get())),
     699          414 :         }
     700          414 :     }
     701              : 
     702              :     /// Returns the configured amount of permits.
     703            0 :     pub fn initial_permits(&self) -> NonZeroUsize {
     704            0 :         self.initial_permits
     705            0 :     }
     706              : }
     707              : 
     708              : impl PartialEq for ConfigurableSemaphore {
     709            0 :     fn eq(&self, other: &Self) -> bool {
     710              :         // the number of permits can be increased at runtime, so we cannot really fulfill the
     711              :         // PartialEq value equality otherwise
     712            0 :         self.initial_permits == other.initial_permits
     713            0 :     }
     714              : }
     715              : 
     716              : impl Eq for ConfigurableSemaphore {}
     717              : 
     718              : impl ConfigurableSemaphore {
     719            0 :     pub fn inner(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
     720            0 :         &self.inner
     721            0 :     }
     722              : }
     723              : 
     724              : #[cfg(test)]
     725              : mod tests {
     726              : 
     727              :     use std::time::Duration;
     728              : 
     729              :     use camino::Utf8PathBuf;
     730              :     use pageserver_api::config::{DiskUsageEvictionTaskConfig, EvictionOrder};
     731              :     use rstest::rstest;
     732              :     use utils::{id::NodeId, serde_percent::Percent};
     733              : 
     734              :     use super::PageServerConf;
     735              : 
     736              :     #[test]
     737            1 :     fn test_minimal_config_toml_is_valid() {
     738              :         // The minimal valid config for running a pageserver:
     739              :         // - control_plane_api is mandatory, as pageservers cannot run in isolation
     740              :         // - we use Default impl of everything else in this situation
     741            1 :         let input = r#"
     742            1 :             control_plane_api = "http://localhost:6666"
     743            1 :         "#;
     744            1 :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     745            1 :             .expect("empty config is valid");
     746            1 :         let workdir = Utf8PathBuf::from("/nonexistent");
     747            1 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir)
     748            1 :             .expect("parse_and_validate");
     749            1 :     }
     750              : 
     751              :     #[test]
     752            1 :     fn test_config_tracing_endpoint_is_invalid() {
     753            1 :         let input = r#"
     754            1 :             control_plane_api = "http://localhost:6666"
     755            1 : 
     756            1 :             [tracing]
     757            1 : 
     758            1 :             sampling_ratio = { numerator = 1, denominator = 0 }
     759            1 : 
     760            1 :             [tracing.export_config]
     761            1 :             endpoint = "localhost:4317"
     762            1 :             protocol = "http-binary"
     763            1 :             timeout = "1ms"
     764            1 :         "#;
     765            1 :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     766            1 :             .expect("config has valid fields");
     767            1 :         let workdir = Utf8PathBuf::from("/nonexistent");
     768            1 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir)
     769            1 :             .expect_err("parse_and_validate should fail for endpoint without scheme");
     770            1 :     }
     771              : 
     772              :     #[rstest]
     773              :     #[case(32, 32, true)]
     774              :     #[case(64, 32, false)]
     775              :     #[case(64, 64, true)]
     776              :     #[case(128, 128, true)]
     777              :     fn test_config_max_batch_size_is_valid(
     778              :         #[case] max_batch_size: usize,
     779              :         #[case] max_get_vectored_keys: usize,
     780              :         #[case] is_valid: bool,
     781              :     ) {
     782              :         let input = format!(
     783              :             r#"
     784              :             control_plane_api = "http://localhost:6666"
     785              :             max_get_vectored_keys = {max_get_vectored_keys}
     786              :             page_service_pipelining = {{ mode="pipelined", execution="concurrent-futures", max_batch_size={max_batch_size}, batching="uniform-lsn" }}
     787              :         "#,
     788              :         );
     789              :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(&input)
     790              :             .expect("config has valid fields");
     791              :         let workdir = Utf8PathBuf::from("/nonexistent");
     792              :         let result = PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir);
     793              :         assert_eq!(result.is_ok(), is_valid);
     794              :     }
     795              : 
     796              :     #[test]
     797            1 :     fn test_config_posthog_config_is_valid() {
     798            1 :         let input = r#"
     799            1 :             control_plane_api = "http://localhost:6666"
     800            1 : 
     801            1 :             [posthog_config]
     802            1 :             server_api_key = "phs_AAA"
     803            1 :             client_api_key = "phc_BBB"
     804            1 :             project_id = "000"
     805            1 :             private_api_url = "https://us.posthog.com"
     806            1 :             public_api_url = "https://us.i.posthog.com"
     807            1 :         "#;
     808            1 :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     809            1 :             .expect("posthogconfig is valid");
     810            1 :         let workdir = Utf8PathBuf::from("/nonexistent");
     811            1 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir)
     812            1 :             .expect("parse_and_validate");
     813            1 :     }
     814              : 
     815              :     #[test]
     816            1 :     fn test_config_posthog_incomplete_config_is_valid() {
     817            1 :         let input = r#"
     818            1 :             control_plane_api = "http://localhost:6666"
     819            1 : 
     820            1 :             [posthog_config]
     821            1 :             server_api_key = "phs_AAA"
     822            1 :             private_api_url = "https://us.posthog.com"
     823            1 :             public_api_url = "https://us.i.posthog.com"
     824            1 :         "#;
     825            1 :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     826            1 :             .expect("posthogconfig is valid");
     827            1 :         let workdir = Utf8PathBuf::from("/nonexistent");
     828            1 :         PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir)
     829            1 :             .expect("parse_and_validate");
     830            1 :     }
     831              : 
     832              :     #[rstest]
     833              :     #[
     834              :         case::omit_the_whole_config(
     835              :             DiskUsageEvictionTaskConfig {
     836              :                 max_usage_pct: Percent::new(80).unwrap(),
     837              :                 min_avail_bytes: 2_000_000_000,
     838              :                 period: Duration::from_secs(60),
     839              :                 eviction_order: Default::default(),
     840              :                 #[cfg(feature = "testing")]
     841              :                 mock_statvfs: None,
     842              :                 enabled: true,
     843              :             },
     844              :         r#"
     845              :             control_plane_api = "http://localhost:6666"
     846              :         "#,
     847              :     )]
     848              :     #[
     849              :         case::omit_enabled_field(
     850              :             DiskUsageEvictionTaskConfig {
     851              :                 max_usage_pct: Percent::new(80).unwrap(),
     852              :                 min_avail_bytes: 1_000_000_000,
     853              :                 period: Duration::from_secs(60),
     854              :                 eviction_order: EvictionOrder::RelativeAccessed {
     855              :                     highest_layer_count_loses_first: true,
     856              :                 },
     857              :                 #[cfg(feature = "testing")]
     858              :                 mock_statvfs: None,
     859              :                 enabled: true,
     860              :             },
     861              :         r#"
     862              :             control_plane_api = "http://localhost:6666"
     863              :             disk_usage_based_eviction = { max_usage_pct = 80, min_avail_bytes = 1000000000, period = "60s" }
     864              :         "#,
     865              :     )]
     866              :     #[case::disabled(
     867              :         DiskUsageEvictionTaskConfig {
     868              :             max_usage_pct: Percent::new(80).unwrap(),
     869              :             min_avail_bytes: 2_000_000_000,
     870              :             period: Duration::from_secs(60),
     871              :             eviction_order: EvictionOrder::RelativeAccessed {
     872              :                 highest_layer_count_loses_first: true,
     873              :             },
     874              :             #[cfg(feature = "testing")]
     875              :             mock_statvfs: None,
     876              :             enabled: false,
     877              :         },
     878              :         r#"
     879              :             control_plane_api = "http://localhost:6666"
     880              :             disk_usage_based_eviction = { enabled = false }
     881              :         "#
     882              :     )]
     883              :     fn test_config_disk_usage_based_eviction_is_valid(
     884              :         #[case] expected_disk_usage_based_eviction: DiskUsageEvictionTaskConfig,
     885              :         #[case] input: &str,
     886              :     ) {
     887              :         let config_toml = toml_edit::de::from_str::<pageserver_api::config::ConfigToml>(input)
     888              :             .expect("disk_usage_based_eviction is valid");
     889              :         let workdir = Utf8PathBuf::from("/nonexistent");
     890              :         let config = PageServerConf::parse_and_validate(NodeId(0), config_toml, &workdir).unwrap();
     891              :         let disk_usage_based_eviction = config.disk_usage_based_eviction;
     892              :         assert_eq!(
     893              :             expected_disk_usage_based_eviction,
     894              :             disk_usage_based_eviction
     895              :         );
     896              :     }
     897              : }
        

Generated by: LCOV version 2.1-beta