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