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