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