TLA Line data Source code
1 : //! Functions for handling page server configuration options
2 : //!
3 : //! Configuration options can be set in the pageserver.toml configuration
4 : //! file, or on the command line.
5 : //! See also `settings.md` for better description on every parameter.
6 :
7 : use anyhow::{anyhow, bail, ensure, Context, Result};
8 : use pageserver_api::shard::TenantShardId;
9 : use remote_storage::{RemotePath, RemoteStorageConfig};
10 : use serde::de::IntoDeserializer;
11 : use std::env;
12 : use storage_broker::Uri;
13 : use utils::crashsafe::path_with_suffix_extension;
14 : use utils::id::ConnectionId;
15 : use utils::logging::SecretString;
16 :
17 : use once_cell::sync::OnceCell;
18 : use reqwest::Url;
19 : use std::num::NonZeroUsize;
20 : use std::str::FromStr;
21 : use std::sync::Arc;
22 : use std::time::Duration;
23 : use toml_edit;
24 : use toml_edit::{Document, Item};
25 :
26 : use camino::{Utf8Path, Utf8PathBuf};
27 : use postgres_backend::AuthType;
28 : use utils::{
29 : id::{NodeId, TimelineId},
30 : logging::LogFormat,
31 : };
32 :
33 : use crate::disk_usage_eviction_task::DiskUsageEvictionTaskConfig;
34 : use crate::tenant::config::TenantConf;
35 : use crate::tenant::config::TenantConfOpt;
36 : use crate::tenant::{
37 : TENANTS_SEGMENT_NAME, TENANT_DELETED_MARKER_FILE_NAME, TIMELINES_SEGMENT_NAME,
38 : };
39 : use crate::{
40 : IGNORED_TENANT_FILE_NAME, METADATA_FILE_NAME, TENANT_CONFIG_NAME, TENANT_HEATMAP_BASENAME,
41 : TENANT_LOCATION_CONFIG_NAME, TIMELINE_DELETE_MARK_SUFFIX, TIMELINE_UNINIT_MARK_SUFFIX,
42 : };
43 :
44 : use self::defaults::DEFAULT_CONCURRENT_TENANT_WARMUP;
45 :
46 : pub mod defaults {
47 : use crate::tenant::config::defaults::*;
48 : use const_format::formatcp;
49 :
50 : pub use pageserver_api::{
51 : DEFAULT_HTTP_LISTEN_ADDR, DEFAULT_HTTP_LISTEN_PORT, DEFAULT_PG_LISTEN_ADDR,
52 : DEFAULT_PG_LISTEN_PORT,
53 : };
54 : pub use storage_broker::DEFAULT_ENDPOINT as BROKER_DEFAULT_ENDPOINT;
55 :
56 : pub const DEFAULT_WAIT_LSN_TIMEOUT: &str = "60 s";
57 : pub const DEFAULT_WAL_REDO_TIMEOUT: &str = "60 s";
58 :
59 : pub const DEFAULT_SUPERUSER: &str = "cloud_admin";
60 :
61 : pub const DEFAULT_PAGE_CACHE_SIZE: usize = 8192;
62 : pub const DEFAULT_MAX_FILE_DESCRIPTORS: usize = 100;
63 :
64 : pub const DEFAULT_LOG_FORMAT: &str = "plain";
65 :
66 : pub const DEFAULT_CONCURRENT_TENANT_WARMUP: usize = 8;
67 :
68 : pub const DEFAULT_CONCURRENT_TENANT_SIZE_LOGICAL_SIZE_QUERIES: usize =
69 : super::ConfigurableSemaphore::DEFAULT_INITIAL.get();
70 :
71 : pub const DEFAULT_METRIC_COLLECTION_INTERVAL: &str = "10 min";
72 : pub const DEFAULT_CACHED_METRIC_COLLECTION_INTERVAL: &str = "0s";
73 : pub const DEFAULT_METRIC_COLLECTION_ENDPOINT: Option<reqwest::Url> = None;
74 : pub const DEFAULT_SYNTHETIC_SIZE_CALCULATION_INTERVAL: &str = "10 min";
75 : pub const DEFAULT_BACKGROUND_TASK_MAXIMUM_DELAY: &str = "10s";
76 :
77 : pub const DEFAULT_HEATMAP_UPLOAD_CONCURRENCY: usize = 8;
78 : pub const DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY: usize = 1;
79 :
80 : pub const DEFAULT_INGEST_BATCH_SIZE: u64 = 100;
81 :
82 : ///
83 : /// Default built-in configuration file.
84 : ///
85 : pub const DEFAULT_CONFIG_FILE: &str = formatcp!(
86 : r#"
87 : # Initial configuration file created by 'pageserver --init'
88 : #listen_pg_addr = '{DEFAULT_PG_LISTEN_ADDR}'
89 : #listen_http_addr = '{DEFAULT_HTTP_LISTEN_ADDR}'
90 :
91 : #wait_lsn_timeout = '{DEFAULT_WAIT_LSN_TIMEOUT}'
92 : #wal_redo_timeout = '{DEFAULT_WAL_REDO_TIMEOUT}'
93 :
94 : #page_cache_size = {DEFAULT_PAGE_CACHE_SIZE}
95 : #max_file_descriptors = {DEFAULT_MAX_FILE_DESCRIPTORS}
96 :
97 : # initial superuser role name to use when creating a new tenant
98 : #initial_superuser_name = '{DEFAULT_SUPERUSER}'
99 :
100 : #broker_endpoint = '{BROKER_DEFAULT_ENDPOINT}'
101 :
102 : #log_format = '{DEFAULT_LOG_FORMAT}'
103 :
104 : #concurrent_tenant_size_logical_size_queries = '{DEFAULT_CONCURRENT_TENANT_SIZE_LOGICAL_SIZE_QUERIES}'
105 : #concurrent_tenant_warmup = '{DEFAULT_CONCURRENT_TENANT_WARMUP}'
106 :
107 : #metric_collection_interval = '{DEFAULT_METRIC_COLLECTION_INTERVAL}'
108 : #cached_metric_collection_interval = '{DEFAULT_CACHED_METRIC_COLLECTION_INTERVAL}'
109 : #synthetic_size_calculation_interval = '{DEFAULT_SYNTHETIC_SIZE_CALCULATION_INTERVAL}'
110 :
111 : #disk_usage_based_eviction = {{ max_usage_pct = .., min_avail_bytes = .., period = "10s"}}
112 :
113 : #background_task_maximum_delay = '{DEFAULT_BACKGROUND_TASK_MAXIMUM_DELAY}'
114 :
115 : #ingest_batch_size = {DEFAULT_INGEST_BATCH_SIZE}
116 :
117 : [tenant_config]
118 : #checkpoint_distance = {DEFAULT_CHECKPOINT_DISTANCE} # in bytes
119 : #checkpoint_timeout = {DEFAULT_CHECKPOINT_TIMEOUT}
120 : #compaction_target_size = {DEFAULT_COMPACTION_TARGET_SIZE} # in bytes
121 : #compaction_period = '{DEFAULT_COMPACTION_PERIOD}'
122 : #compaction_threshold = {DEFAULT_COMPACTION_THRESHOLD}
123 :
124 : #gc_period = '{DEFAULT_GC_PERIOD}'
125 : #gc_horizon = {DEFAULT_GC_HORIZON}
126 : #image_creation_threshold = {DEFAULT_IMAGE_CREATION_THRESHOLD}
127 : #pitr_interval = '{DEFAULT_PITR_INTERVAL}'
128 :
129 : #min_resident_size_override = .. # in bytes
130 : #evictions_low_residence_duration_metric_threshold = '{DEFAULT_EVICTIONS_LOW_RESIDENCE_DURATION_METRIC_THRESHOLD}'
131 : #gc_feedback = false
132 :
133 : #heatmap_upload_concurrency = {DEFAULT_HEATMAP_UPLOAD_CONCURRENCY}
134 : #secondary_download_concurrency = {DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY}
135 :
136 : [remote_storage]
137 :
138 : "#
139 : );
140 : }
141 :
142 CBC 2 : #[derive(Debug, Clone, PartialEq, Eq)]
143 : pub struct PageServerConf {
144 : // Identifier of that particular pageserver so e g safekeepers
145 : // can safely distinguish different pageservers
146 : pub id: NodeId,
147 :
148 : /// Example (default): 127.0.0.1:64000
149 : pub listen_pg_addr: String,
150 : /// Example (default): 127.0.0.1:9898
151 : pub listen_http_addr: String,
152 :
153 : /// Current availability zone. Used for traffic metrics.
154 : pub availability_zone: Option<String>,
155 :
156 : // Timeout when waiting for WAL receiver to catch up to an LSN given in a GetPage@LSN call.
157 : pub wait_lsn_timeout: Duration,
158 : // How long to wait for WAL redo to complete.
159 : pub wal_redo_timeout: Duration,
160 :
161 : pub superuser: String,
162 :
163 : pub page_cache_size: usize,
164 : pub max_file_descriptors: usize,
165 :
166 : // Repository directory, relative to current working directory.
167 : // Normally, the page server changes the current working directory
168 : // to the repository, and 'workdir' is always '.'. But we don't do
169 : // that during unit testing, because the current directory is global
170 : // to the process but different unit tests work on different
171 : // repositories.
172 : pub workdir: Utf8PathBuf,
173 :
174 : pub pg_distrib_dir: Utf8PathBuf,
175 :
176 : // Authentication
177 : /// authentication method for the HTTP mgmt API
178 : pub http_auth_type: AuthType,
179 : /// authentication method for libpq connections from compute
180 : pub pg_auth_type: AuthType,
181 : /// Path to a file or directory containing public key(s) for verifying JWT tokens.
182 : /// Used for both mgmt and compute auth, if enabled.
183 : pub auth_validation_public_key_path: Option<Utf8PathBuf>,
184 :
185 : pub remote_storage_config: Option<RemoteStorageConfig>,
186 :
187 : pub default_tenant_conf: TenantConf,
188 :
189 : /// Storage broker endpoints to connect to.
190 : pub broker_endpoint: Uri,
191 : pub broker_keepalive_interval: Duration,
192 :
193 : pub log_format: LogFormat,
194 :
195 : /// Number of tenants which will be concurrently loaded from remote storage proactively on startup,
196 : /// does not limit tenants loaded in response to client I/O. A lower value implicitly deprioritizes
197 : /// loading such tenants, vs. other work in the system.
198 : pub concurrent_tenant_warmup: ConfigurableSemaphore,
199 :
200 : /// Number of concurrent [`Tenant::gather_size_inputs`](crate::tenant::Tenant::gather_size_inputs) allowed.
201 : pub concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore,
202 : /// Limit of concurrent [`Tenant::gather_size_inputs`] issued by module `eviction_task`.
203 : /// The number of permits is the same as `concurrent_tenant_size_logical_size_queries`.
204 : /// See the comment in `eviction_task` for details.
205 : ///
206 : /// [`Tenant::gather_size_inputs`]: crate::tenant::Tenant::gather_size_inputs
207 : pub eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore,
208 :
209 : // How often to collect metrics and send them to the metrics endpoint.
210 : pub metric_collection_interval: Duration,
211 : // How often to send unchanged cached metrics to the metrics endpoint.
212 : pub cached_metric_collection_interval: Duration,
213 : pub metric_collection_endpoint: Option<Url>,
214 : pub synthetic_size_calculation_interval: Duration,
215 :
216 : pub disk_usage_based_eviction: Option<DiskUsageEvictionTaskConfig>,
217 :
218 : pub test_remote_failures: u64,
219 :
220 : pub ondemand_download_behavior_treat_error_as_warn: bool,
221 :
222 : /// How long will background tasks be delayed at most after initial load of tenants.
223 : ///
224 : /// Our largest initialization completions are in the range of 100-200s, so perhaps 10s works
225 : /// as we now isolate initial loading, initial logical size calculation and background tasks.
226 : /// Smaller nodes will have background tasks "not running" for this long unless every timeline
227 : /// has it's initial logical size calculated. Not running background tasks for some seconds is
228 : /// not terrible.
229 : pub background_task_maximum_delay: Duration,
230 :
231 : pub control_plane_api: Option<Url>,
232 :
233 : /// JWT token for use with the control plane API.
234 : pub control_plane_api_token: Option<SecretString>,
235 :
236 : /// If true, pageserver will make best-effort to operate without a control plane: only
237 : /// for use in major incidents.
238 : pub control_plane_emergency_mode: bool,
239 :
240 : /// How many heatmap uploads may be done concurrency: lower values implicitly deprioritize
241 : /// heatmap uploads vs. other remote storage operations.
242 : pub heatmap_upload_concurrency: usize,
243 :
244 : /// How many remote storage downloads may be done for secondary tenants concurrently. Implicitly
245 : /// deprioritises secondary downloads vs. remote storage operations for attached tenants.
246 : pub secondary_download_concurrency: usize,
247 :
248 : /// Maximum number of WAL records to be ingested and committed at the same time
249 : pub ingest_batch_size: u64,
250 : }
251 :
252 : /// We do not want to store this in a PageServerConf because the latter may be logged
253 : /// and/or serialized at a whim, while the token is secret. Currently this token is the
254 : /// same for accessing all tenants/timelines, but may become per-tenant/per-timeline in
255 : /// the future, more tokens and auth may arrive for storage broker, completely changing the logic.
256 : /// Hence, we resort to a global variable for now instead of passing the token from the
257 : /// startup code to the connection code through a dozen layers.
258 : pub static SAFEKEEPER_AUTH_TOKEN: OnceCell<Arc<String>> = OnceCell::new();
259 :
260 : // use dedicated enum for builder to better indicate the intention
261 : // and avoid possible confusion with nested options
262 : pub enum BuilderValue<T> {
263 : Set(T),
264 : NotSet,
265 : }
266 :
267 : impl<T> BuilderValue<T> {
268 31127 : pub fn ok_or<E>(self, err: E) -> Result<T, E> {
269 31127 : match self {
270 31126 : Self::Set(v) => Ok(v),
271 1 : Self::NotSet => Err(err),
272 : }
273 31127 : }
274 : }
275 :
276 : // needed to simplify config construction
277 : struct PageServerConfigBuilder {
278 : listen_pg_addr: BuilderValue<String>,
279 :
280 : listen_http_addr: BuilderValue<String>,
281 :
282 : availability_zone: BuilderValue<Option<String>>,
283 :
284 : wait_lsn_timeout: BuilderValue<Duration>,
285 : wal_redo_timeout: BuilderValue<Duration>,
286 :
287 : superuser: BuilderValue<String>,
288 :
289 : page_cache_size: BuilderValue<usize>,
290 : max_file_descriptors: BuilderValue<usize>,
291 :
292 : workdir: BuilderValue<Utf8PathBuf>,
293 :
294 : pg_distrib_dir: BuilderValue<Utf8PathBuf>,
295 :
296 : http_auth_type: BuilderValue<AuthType>,
297 : pg_auth_type: BuilderValue<AuthType>,
298 :
299 : //
300 : auth_validation_public_key_path: BuilderValue<Option<Utf8PathBuf>>,
301 : remote_storage_config: BuilderValue<Option<RemoteStorageConfig>>,
302 :
303 : id: BuilderValue<NodeId>,
304 :
305 : broker_endpoint: BuilderValue<Uri>,
306 : broker_keepalive_interval: BuilderValue<Duration>,
307 :
308 : log_format: BuilderValue<LogFormat>,
309 :
310 : concurrent_tenant_warmup: BuilderValue<NonZeroUsize>,
311 : concurrent_tenant_size_logical_size_queries: BuilderValue<NonZeroUsize>,
312 :
313 : metric_collection_interval: BuilderValue<Duration>,
314 : cached_metric_collection_interval: BuilderValue<Duration>,
315 : metric_collection_endpoint: BuilderValue<Option<Url>>,
316 : synthetic_size_calculation_interval: BuilderValue<Duration>,
317 :
318 : disk_usage_based_eviction: BuilderValue<Option<DiskUsageEvictionTaskConfig>>,
319 :
320 : test_remote_failures: BuilderValue<u64>,
321 :
322 : ondemand_download_behavior_treat_error_as_warn: BuilderValue<bool>,
323 :
324 : background_task_maximum_delay: BuilderValue<Duration>,
325 :
326 : control_plane_api: BuilderValue<Option<Url>>,
327 : control_plane_api_token: BuilderValue<Option<SecretString>>,
328 : control_plane_emergency_mode: BuilderValue<bool>,
329 :
330 : heatmap_upload_concurrency: BuilderValue<usize>,
331 : secondary_download_concurrency: BuilderValue<usize>,
332 :
333 : ingest_batch_size: BuilderValue<u64>,
334 : }
335 :
336 : impl Default for PageServerConfigBuilder {
337 916 : fn default() -> Self {
338 916 : use self::BuilderValue::*;
339 916 : use defaults::*;
340 916 : Self {
341 916 : listen_pg_addr: Set(DEFAULT_PG_LISTEN_ADDR.to_string()),
342 916 : listen_http_addr: Set(DEFAULT_HTTP_LISTEN_ADDR.to_string()),
343 916 : availability_zone: Set(None),
344 916 : wait_lsn_timeout: Set(humantime::parse_duration(DEFAULT_WAIT_LSN_TIMEOUT)
345 916 : .expect("cannot parse default wait lsn timeout")),
346 916 : wal_redo_timeout: Set(humantime::parse_duration(DEFAULT_WAL_REDO_TIMEOUT)
347 916 : .expect("cannot parse default wal redo timeout")),
348 916 : superuser: Set(DEFAULT_SUPERUSER.to_string()),
349 916 : page_cache_size: Set(DEFAULT_PAGE_CACHE_SIZE),
350 916 : max_file_descriptors: Set(DEFAULT_MAX_FILE_DESCRIPTORS),
351 916 : workdir: Set(Utf8PathBuf::new()),
352 916 : pg_distrib_dir: Set(Utf8PathBuf::from_path_buf(
353 916 : env::current_dir().expect("cannot access current directory"),
354 916 : )
355 916 : .expect("non-Unicode path")
356 916 : .join("pg_install")),
357 916 : http_auth_type: Set(AuthType::Trust),
358 916 : pg_auth_type: Set(AuthType::Trust),
359 916 : auth_validation_public_key_path: Set(None),
360 916 : remote_storage_config: Set(None),
361 916 : id: NotSet,
362 916 : broker_endpoint: Set(storage_broker::DEFAULT_ENDPOINT
363 916 : .parse()
364 916 : .expect("failed to parse default broker endpoint")),
365 916 : broker_keepalive_interval: Set(humantime::parse_duration(
366 916 : storage_broker::DEFAULT_KEEPALIVE_INTERVAL,
367 916 : )
368 916 : .expect("cannot parse default keepalive interval")),
369 916 : log_format: Set(LogFormat::from_str(DEFAULT_LOG_FORMAT).unwrap()),
370 916 :
371 916 : concurrent_tenant_warmup: Set(NonZeroUsize::new(DEFAULT_CONCURRENT_TENANT_WARMUP)
372 916 : .expect("Invalid default constant")),
373 916 : concurrent_tenant_size_logical_size_queries: Set(
374 916 : ConfigurableSemaphore::DEFAULT_INITIAL,
375 916 : ),
376 916 : metric_collection_interval: Set(humantime::parse_duration(
377 916 : DEFAULT_METRIC_COLLECTION_INTERVAL,
378 916 : )
379 916 : .expect("cannot parse default metric collection interval")),
380 916 : cached_metric_collection_interval: Set(humantime::parse_duration(
381 916 : DEFAULT_CACHED_METRIC_COLLECTION_INTERVAL,
382 916 : )
383 916 : .expect("cannot parse default cached_metric_collection_interval")),
384 916 : synthetic_size_calculation_interval: Set(humantime::parse_duration(
385 916 : DEFAULT_SYNTHETIC_SIZE_CALCULATION_INTERVAL,
386 916 : )
387 916 : .expect("cannot parse default synthetic size calculation interval")),
388 916 : metric_collection_endpoint: Set(DEFAULT_METRIC_COLLECTION_ENDPOINT),
389 916 :
390 916 : disk_usage_based_eviction: Set(None),
391 916 :
392 916 : test_remote_failures: Set(0),
393 916 :
394 916 : ondemand_download_behavior_treat_error_as_warn: Set(false),
395 916 :
396 916 : background_task_maximum_delay: Set(humantime::parse_duration(
397 916 : DEFAULT_BACKGROUND_TASK_MAXIMUM_DELAY,
398 916 : )
399 916 : .unwrap()),
400 916 :
401 916 : control_plane_api: Set(None),
402 916 : control_plane_api_token: Set(None),
403 916 : control_plane_emergency_mode: Set(false),
404 916 :
405 916 : heatmap_upload_concurrency: Set(DEFAULT_HEATMAP_UPLOAD_CONCURRENCY),
406 916 : secondary_download_concurrency: Set(DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY),
407 916 :
408 916 : ingest_batch_size: Set(DEFAULT_INGEST_BATCH_SIZE),
409 916 : }
410 916 : }
411 : }
412 :
413 : impl PageServerConfigBuilder {
414 912 : pub fn listen_pg_addr(&mut self, listen_pg_addr: String) {
415 912 : self.listen_pg_addr = BuilderValue::Set(listen_pg_addr)
416 912 : }
417 :
418 912 : pub fn listen_http_addr(&mut self, listen_http_addr: String) {
419 912 : self.listen_http_addr = BuilderValue::Set(listen_http_addr)
420 912 : }
421 :
422 2 : pub fn availability_zone(&mut self, availability_zone: Option<String>) {
423 2 : self.availability_zone = BuilderValue::Set(availability_zone)
424 2 : }
425 :
426 14 : pub fn wait_lsn_timeout(&mut self, wait_lsn_timeout: Duration) {
427 14 : self.wait_lsn_timeout = BuilderValue::Set(wait_lsn_timeout)
428 14 : }
429 :
430 6 : pub fn wal_redo_timeout(&mut self, wal_redo_timeout: Duration) {
431 6 : self.wal_redo_timeout = BuilderValue::Set(wal_redo_timeout)
432 6 : }
433 :
434 6 : pub fn superuser(&mut self, superuser: String) {
435 6 : self.superuser = BuilderValue::Set(superuser)
436 6 : }
437 :
438 10 : pub fn page_cache_size(&mut self, page_cache_size: usize) {
439 10 : self.page_cache_size = BuilderValue::Set(page_cache_size)
440 10 : }
441 :
442 6 : pub fn max_file_descriptors(&mut self, max_file_descriptors: usize) {
443 6 : self.max_file_descriptors = BuilderValue::Set(max_file_descriptors)
444 6 : }
445 :
446 916 : pub fn workdir(&mut self, workdir: Utf8PathBuf) {
447 916 : self.workdir = BuilderValue::Set(workdir)
448 916 : }
449 :
450 916 : pub fn pg_distrib_dir(&mut self, pg_distrib_dir: Utf8PathBuf) {
451 916 : self.pg_distrib_dir = BuilderValue::Set(pg_distrib_dir)
452 916 : }
453 :
454 906 : pub fn http_auth_type(&mut self, auth_type: AuthType) {
455 906 : self.http_auth_type = BuilderValue::Set(auth_type)
456 906 : }
457 :
458 906 : pub fn pg_auth_type(&mut self, auth_type: AuthType) {
459 906 : self.pg_auth_type = BuilderValue::Set(auth_type)
460 906 : }
461 :
462 22 : pub fn auth_validation_public_key_path(
463 22 : &mut self,
464 22 : auth_validation_public_key_path: Option<Utf8PathBuf>,
465 22 : ) {
466 22 : self.auth_validation_public_key_path = BuilderValue::Set(auth_validation_public_key_path)
467 22 : }
468 :
469 912 : pub fn remote_storage_config(&mut self, remote_storage_config: Option<RemoteStorageConfig>) {
470 912 : self.remote_storage_config = BuilderValue::Set(remote_storage_config)
471 912 : }
472 :
473 913 : pub fn broker_endpoint(&mut self, broker_endpoint: Uri) {
474 913 : self.broker_endpoint = BuilderValue::Set(broker_endpoint)
475 913 : }
476 :
477 UBC 0 : pub fn broker_keepalive_interval(&mut self, broker_keepalive_interval: Duration) {
478 0 : self.broker_keepalive_interval = BuilderValue::Set(broker_keepalive_interval)
479 0 : }
480 :
481 CBC 915 : pub fn id(&mut self, node_id: NodeId) {
482 915 : self.id = BuilderValue::Set(node_id)
483 915 : }
484 :
485 6 : pub fn log_format(&mut self, log_format: LogFormat) {
486 6 : self.log_format = BuilderValue::Set(log_format)
487 6 : }
488 :
489 5 : pub fn concurrent_tenant_warmup(&mut self, u: NonZeroUsize) {
490 5 : self.concurrent_tenant_warmup = BuilderValue::Set(u);
491 5 : }
492 :
493 UBC 0 : pub fn concurrent_tenant_size_logical_size_queries(&mut self, u: NonZeroUsize) {
494 0 : self.concurrent_tenant_size_logical_size_queries = BuilderValue::Set(u);
495 0 : }
496 :
497 CBC 16 : pub fn metric_collection_interval(&mut self, metric_collection_interval: Duration) {
498 16 : self.metric_collection_interval = BuilderValue::Set(metric_collection_interval)
499 16 : }
500 :
501 12 : pub fn cached_metric_collection_interval(
502 12 : &mut self,
503 12 : cached_metric_collection_interval: Duration,
504 12 : ) {
505 12 : self.cached_metric_collection_interval =
506 12 : BuilderValue::Set(cached_metric_collection_interval)
507 12 : }
508 :
509 16 : pub fn metric_collection_endpoint(&mut self, metric_collection_endpoint: Option<Url>) {
510 16 : self.metric_collection_endpoint = BuilderValue::Set(metric_collection_endpoint)
511 16 : }
512 :
513 15 : pub fn synthetic_size_calculation_interval(
514 15 : &mut self,
515 15 : synthetic_size_calculation_interval: Duration,
516 15 : ) {
517 15 : self.synthetic_size_calculation_interval =
518 15 : BuilderValue::Set(synthetic_size_calculation_interval)
519 15 : }
520 :
521 100 : pub fn test_remote_failures(&mut self, fail_first: u64) {
522 100 : self.test_remote_failures = BuilderValue::Set(fail_first);
523 100 : }
524 :
525 4 : pub fn disk_usage_based_eviction(&mut self, value: Option<DiskUsageEvictionTaskConfig>) {
526 4 : self.disk_usage_based_eviction = BuilderValue::Set(value);
527 4 : }
528 :
529 UBC 0 : pub fn ondemand_download_behavior_treat_error_as_warn(
530 0 : &mut self,
531 0 : ondemand_download_behavior_treat_error_as_warn: bool,
532 0 : ) {
533 0 : self.ondemand_download_behavior_treat_error_as_warn =
534 0 : BuilderValue::Set(ondemand_download_behavior_treat_error_as_warn);
535 0 : }
536 :
537 CBC 9 : pub fn background_task_maximum_delay(&mut self, delay: Duration) {
538 9 : self.background_task_maximum_delay = BuilderValue::Set(delay);
539 9 : }
540 :
541 906 : pub fn control_plane_api(&mut self, api: Option<Url>) {
542 906 : self.control_plane_api = BuilderValue::Set(api)
543 906 : }
544 :
545 UBC 0 : pub fn control_plane_api_token(&mut self, token: Option<SecretString>) {
546 0 : self.control_plane_api_token = BuilderValue::Set(token)
547 0 : }
548 :
549 CBC 1 : pub fn control_plane_emergency_mode(&mut self, enabled: bool) {
550 1 : self.control_plane_emergency_mode = BuilderValue::Set(enabled)
551 1 : }
552 :
553 UBC 0 : pub fn heatmap_upload_concurrency(&mut self, value: usize) {
554 0 : self.heatmap_upload_concurrency = BuilderValue::Set(value)
555 0 : }
556 :
557 0 : pub fn secondary_download_concurrency(&mut self, value: usize) {
558 0 : self.secondary_download_concurrency = BuilderValue::Set(value)
559 0 : }
560 :
561 0 : pub fn ingest_batch_size(&mut self, ingest_batch_size: u64) {
562 0 : self.ingest_batch_size = BuilderValue::Set(ingest_batch_size)
563 0 : }
564 :
565 CBC 916 : pub fn build(self) -> anyhow::Result<PageServerConf> {
566 916 : let concurrent_tenant_warmup = self
567 916 : .concurrent_tenant_warmup
568 916 : .ok_or(anyhow!("missing concurrent_tenant_warmup"))?;
569 916 : let concurrent_tenant_size_logical_size_queries = self
570 916 : .concurrent_tenant_size_logical_size_queries
571 916 : .ok_or(anyhow!(
572 916 : "missing concurrent_tenant_size_logical_size_queries"
573 916 : ))?;
574 : Ok(PageServerConf {
575 916 : listen_pg_addr: self
576 916 : .listen_pg_addr
577 916 : .ok_or(anyhow!("missing listen_pg_addr"))?,
578 916 : listen_http_addr: self
579 916 : .listen_http_addr
580 916 : .ok_or(anyhow!("missing listen_http_addr"))?,
581 916 : availability_zone: self
582 916 : .availability_zone
583 916 : .ok_or(anyhow!("missing availability_zone"))?,
584 916 : wait_lsn_timeout: self
585 916 : .wait_lsn_timeout
586 916 : .ok_or(anyhow!("missing wait_lsn_timeout"))?,
587 916 : wal_redo_timeout: self
588 916 : .wal_redo_timeout
589 916 : .ok_or(anyhow!("missing wal_redo_timeout"))?,
590 916 : superuser: self.superuser.ok_or(anyhow!("missing superuser"))?,
591 916 : page_cache_size: self
592 916 : .page_cache_size
593 916 : .ok_or(anyhow!("missing page_cache_size"))?,
594 916 : max_file_descriptors: self
595 916 : .max_file_descriptors
596 916 : .ok_or(anyhow!("missing max_file_descriptors"))?,
597 916 : workdir: self.workdir.ok_or(anyhow!("missing workdir"))?,
598 916 : pg_distrib_dir: self
599 916 : .pg_distrib_dir
600 916 : .ok_or(anyhow!("missing pg_distrib_dir"))?,
601 916 : http_auth_type: self
602 916 : .http_auth_type
603 916 : .ok_or(anyhow!("missing http_auth_type"))?,
604 916 : pg_auth_type: self.pg_auth_type.ok_or(anyhow!("missing pg_auth_type"))?,
605 916 : auth_validation_public_key_path: self
606 916 : .auth_validation_public_key_path
607 916 : .ok_or(anyhow!("missing auth_validation_public_key_path"))?,
608 916 : remote_storage_config: self
609 916 : .remote_storage_config
610 916 : .ok_or(anyhow!("missing remote_storage_config"))?,
611 916 : id: self.id.ok_or(anyhow!("missing id"))?,
612 : // TenantConf is handled separately
613 915 : default_tenant_conf: TenantConf::default(),
614 915 : broker_endpoint: self
615 915 : .broker_endpoint
616 915 : .ok_or(anyhow!("No broker endpoints provided"))?,
617 915 : broker_keepalive_interval: self
618 915 : .broker_keepalive_interval
619 915 : .ok_or(anyhow!("No broker keepalive interval provided"))?,
620 915 : log_format: self.log_format.ok_or(anyhow!("missing log_format"))?,
621 915 : concurrent_tenant_warmup: ConfigurableSemaphore::new(concurrent_tenant_warmup),
622 915 : concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::new(
623 915 : concurrent_tenant_size_logical_size_queries,
624 915 : ),
625 915 : eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore::new(
626 915 : concurrent_tenant_size_logical_size_queries,
627 915 : ),
628 915 : metric_collection_interval: self
629 915 : .metric_collection_interval
630 915 : .ok_or(anyhow!("missing metric_collection_interval"))?,
631 915 : cached_metric_collection_interval: self
632 915 : .cached_metric_collection_interval
633 915 : .ok_or(anyhow!("missing cached_metric_collection_interval"))?,
634 915 : metric_collection_endpoint: self
635 915 : .metric_collection_endpoint
636 915 : .ok_or(anyhow!("missing metric_collection_endpoint"))?,
637 915 : synthetic_size_calculation_interval: self
638 915 : .synthetic_size_calculation_interval
639 915 : .ok_or(anyhow!("missing synthetic_size_calculation_interval"))?,
640 915 : disk_usage_based_eviction: self
641 915 : .disk_usage_based_eviction
642 915 : .ok_or(anyhow!("missing disk_usage_based_eviction"))?,
643 915 : test_remote_failures: self
644 915 : .test_remote_failures
645 915 : .ok_or(anyhow!("missing test_remote_failuers"))?,
646 915 : ondemand_download_behavior_treat_error_as_warn: self
647 915 : .ondemand_download_behavior_treat_error_as_warn
648 915 : .ok_or(anyhow!(
649 915 : "missing ondemand_download_behavior_treat_error_as_warn"
650 915 : ))?,
651 915 : background_task_maximum_delay: self
652 915 : .background_task_maximum_delay
653 915 : .ok_or(anyhow!("missing background_task_maximum_delay"))?,
654 915 : control_plane_api: self
655 915 : .control_plane_api
656 915 : .ok_or(anyhow!("missing control_plane_api"))?,
657 915 : control_plane_api_token: self
658 915 : .control_plane_api_token
659 915 : .ok_or(anyhow!("missing control_plane_api_token"))?,
660 915 : control_plane_emergency_mode: self
661 915 : .control_plane_emergency_mode
662 915 : .ok_or(anyhow!("missing control_plane_emergency_mode"))?,
663 915 : heatmap_upload_concurrency: self
664 915 : .heatmap_upload_concurrency
665 915 : .ok_or(anyhow!("missing heatmap_upload_concurrency"))?,
666 915 : secondary_download_concurrency: self
667 915 : .secondary_download_concurrency
668 915 : .ok_or(anyhow!("missing secondary_download_concurrency"))?,
669 915 : ingest_batch_size: self
670 915 : .ingest_batch_size
671 915 : .ok_or(anyhow!("missing ingest_batch_size"))?,
672 : })
673 916 : }
674 : }
675 :
676 : impl PageServerConf {
677 : //
678 : // Repository paths, relative to workdir.
679 : //
680 :
681 138893 : pub fn tenants_path(&self) -> Utf8PathBuf {
682 138893 : self.workdir.join(TENANTS_SEGMENT_NAME)
683 138893 : }
684 :
685 2408 : pub fn deletion_prefix(&self) -> Utf8PathBuf {
686 2408 : self.workdir.join("deletion")
687 2408 : }
688 :
689 128 : pub fn deletion_list_path(&self, sequence: u64) -> Utf8PathBuf {
690 128 : // Encode a version in the filename, so that if we ever switch away from JSON we can
691 128 : // increment this.
692 128 : const VERSION: u8 = 1;
693 128 :
694 128 : self.deletion_prefix()
695 128 : .join(format!("{sequence:016x}-{VERSION:02x}.list"))
696 128 : }
697 :
698 1157 : pub fn deletion_header_path(&self) -> Utf8PathBuf {
699 1157 : // Encode a version in the filename, so that if we ever switch away from JSON we can
700 1157 : // increment this.
701 1157 : const VERSION: u8 = 1;
702 1157 :
703 1157 : self.deletion_prefix().join(format!("header-{VERSION:02x}"))
704 1157 : }
705 :
706 137428 : pub fn tenant_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
707 137428 : self.tenants_path().join(tenant_shard_id.to_string())
708 137428 : }
709 :
710 1492 : pub fn tenant_ignore_mark_file_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
711 1492 : self.tenant_path(tenant_shard_id)
712 1492 : .join(IGNORED_TENANT_FILE_NAME)
713 1492 : }
714 :
715 : /// Points to a place in pageserver's local directory,
716 : /// where certain tenant's tenantconf file should be located.
717 : ///
718 : /// Legacy: superseded by tenant_location_config_path. Eventually
719 : /// remove this function.
720 1105 : pub fn tenant_config_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
721 1105 : self.tenant_path(tenant_shard_id).join(TENANT_CONFIG_NAME)
722 1105 : }
723 :
724 1105 : pub fn tenant_location_config_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
725 1105 : self.tenant_path(tenant_shard_id)
726 1105 : .join(TENANT_LOCATION_CONFIG_NAME)
727 1105 : }
728 :
729 6 : pub(crate) fn tenant_heatmap_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
730 6 : self.tenant_path(tenant_shard_id)
731 6 : .join(TENANT_HEATMAP_BASENAME)
732 6 : }
733 :
734 131615 : pub fn timelines_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
735 131615 : self.tenant_path(tenant_shard_id)
736 131615 : .join(TIMELINES_SEGMENT_NAME)
737 131615 : }
738 :
739 129431 : pub fn timeline_path(
740 129431 : &self,
741 129431 : tenant_shard_id: &TenantShardId,
742 129431 : timeline_id: &TimelineId,
743 129431 : ) -> Utf8PathBuf {
744 129431 : self.timelines_path(tenant_shard_id)
745 129431 : .join(timeline_id.to_string())
746 129431 : }
747 :
748 942 : pub fn timeline_uninit_mark_file_path(
749 942 : &self,
750 942 : tenant_shard_id: TenantShardId,
751 942 : timeline_id: TimelineId,
752 942 : ) -> Utf8PathBuf {
753 942 : path_with_suffix_extension(
754 942 : self.timeline_path(&tenant_shard_id, &timeline_id),
755 942 : TIMELINE_UNINIT_MARK_SUFFIX,
756 942 : )
757 942 : }
758 :
759 154 : pub fn timeline_delete_mark_file_path(
760 154 : &self,
761 154 : tenant_shard_id: TenantShardId,
762 154 : timeline_id: TimelineId,
763 154 : ) -> Utf8PathBuf {
764 154 : path_with_suffix_extension(
765 154 : self.timeline_path(&tenant_shard_id, &timeline_id),
766 154 : TIMELINE_DELETE_MARK_SUFFIX,
767 154 : )
768 154 : }
769 :
770 851 : pub fn tenant_deleted_mark_file_path(&self, tenant_shard_id: &TenantShardId) -> Utf8PathBuf {
771 851 : self.tenant_path(tenant_shard_id)
772 851 : .join(TENANT_DELETED_MARKER_FILE_NAME)
773 851 : }
774 :
775 4 : pub fn traces_path(&self) -> Utf8PathBuf {
776 4 : self.workdir.join("traces")
777 4 : }
778 :
779 4 : pub fn trace_path(
780 4 : &self,
781 4 : tenant_shard_id: &TenantShardId,
782 4 : timeline_id: &TimelineId,
783 4 : connection_id: &ConnectionId,
784 4 : ) -> Utf8PathBuf {
785 4 : self.traces_path()
786 4 : .join(tenant_shard_id.to_string())
787 4 : .join(timeline_id.to_string())
788 4 : .join(connection_id.to_string())
789 4 : }
790 :
791 : /// Points to a place in pageserver's local directory,
792 : /// where certain timeline's metadata file should be located.
793 6072 : pub fn metadata_path(
794 6072 : &self,
795 6072 : tenant_shard_id: &TenantShardId,
796 6072 : timeline_id: &TimelineId,
797 6072 : ) -> Utf8PathBuf {
798 6072 : self.timeline_path(tenant_shard_id, timeline_id)
799 6072 : .join(METADATA_FILE_NAME)
800 6072 : }
801 :
802 : /// Turns storage remote path of a file into its local path.
803 UBC 0 : pub fn local_path(&self, remote_path: &RemotePath) -> Utf8PathBuf {
804 0 : remote_path.with_base(&self.workdir)
805 0 : }
806 :
807 : //
808 : // Postgres distribution paths
809 : //
810 CBC 2134 : pub fn pg_distrib_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
811 2134 : let path = self.pg_distrib_dir.clone();
812 2134 :
813 2134 : #[allow(clippy::manual_range_patterns)]
814 2134 : match pg_version {
815 2134 : 14 | 15 | 16 => Ok(path.join(format!("v{pg_version}"))),
816 UBC 0 : _ => bail!("Unsupported postgres version: {}", pg_version),
817 : }
818 CBC 2134 : }
819 :
820 1067 : pub fn pg_bin_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
821 1067 : Ok(self.pg_distrib_dir(pg_version)?.join("bin"))
822 1067 : }
823 1067 : pub fn pg_lib_dir(&self, pg_version: u32) -> anyhow::Result<Utf8PathBuf> {
824 1067 : Ok(self.pg_distrib_dir(pg_version)?.join("lib"))
825 1067 : }
826 :
827 : /// Parse a configuration file (pageserver.toml) into a PageServerConf struct,
828 : /// validating the input and failing on errors.
829 : ///
830 : /// This leaves any options not present in the file in the built-in defaults.
831 916 : pub fn parse_and_validate(toml: &Document, workdir: &Utf8Path) -> anyhow::Result<Self> {
832 916 : let mut builder = PageServerConfigBuilder::default();
833 916 : builder.workdir(workdir.to_owned());
834 916 :
835 916 : let mut t_conf = TenantConfOpt::default();
836 :
837 9358 : for (key, item) in toml.iter() {
838 9358 : match key {
839 9358 : "listen_pg_addr" => builder.listen_pg_addr(parse_toml_string(key, item)?),
840 8446 : "listen_http_addr" => builder.listen_http_addr(parse_toml_string(key, item)?),
841 7534 : "availability_zone" => builder.availability_zone(Some(parse_toml_string(key, item)?)),
842 7532 : "wait_lsn_timeout" => builder.wait_lsn_timeout(parse_toml_duration(key, item)?),
843 7518 : "wal_redo_timeout" => builder.wal_redo_timeout(parse_toml_duration(key, item)?),
844 7512 : "initial_superuser_name" => builder.superuser(parse_toml_string(key, item)?),
845 7506 : "page_cache_size" => builder.page_cache_size(parse_toml_u64(key, item)? as usize),
846 7496 : "max_file_descriptors" => {
847 6 : builder.max_file_descriptors(parse_toml_u64(key, item)? as usize)
848 : }
849 7490 : "pg_distrib_dir" => {
850 916 : builder.pg_distrib_dir(Utf8PathBuf::from(parse_toml_string(key, item)?))
851 : }
852 6574 : "auth_validation_public_key_path" => builder.auth_validation_public_key_path(Some(
853 22 : Utf8PathBuf::from(parse_toml_string(key, item)?),
854 : )),
855 6552 : "http_auth_type" => builder.http_auth_type(parse_toml_from_str(key, item)?),
856 5646 : "pg_auth_type" => builder.pg_auth_type(parse_toml_from_str(key, item)?),
857 4740 : "remote_storage" => {
858 912 : builder.remote_storage_config(RemoteStorageConfig::from_toml(item)?)
859 : }
860 3828 : "tenant_config" => {
861 910 : t_conf = TenantConfOpt::try_from(item.to_owned()).context(format!("failed to parse: '{key}'"))?;
862 : }
863 2918 : "id" => builder.id(NodeId(parse_toml_u64(key, item)?)),
864 2003 : "broker_endpoint" => builder.broker_endpoint(parse_toml_string(key, item)?.parse().context("failed to parse broker endpoint")?),
865 1090 : "broker_keepalive_interval" => builder.broker_keepalive_interval(parse_toml_duration(key, item)?),
866 1090 : "log_format" => builder.log_format(
867 6 : LogFormat::from_config(&parse_toml_string(key, item)?)?
868 : ),
869 1084 : "concurrent_tenant_warmup" => builder.concurrent_tenant_warmup({
870 5 : let input = parse_toml_string(key, item)?;
871 5 : let permits = input.parse::<usize>().context("expected a number of initial permits, not {s:?}")?;
872 5 : NonZeroUsize::new(permits).context("initial semaphore permits out of range: 0, use other configuration to disable a feature")?
873 : }),
874 1079 : "concurrent_tenant_size_logical_size_queries" => builder.concurrent_tenant_size_logical_size_queries({
875 UBC 0 : let input = parse_toml_string(key, item)?;
876 0 : let permits = input.parse::<usize>().context("expected a number of initial permits, not {s:?}")?;
877 0 : NonZeroUsize::new(permits).context("initial semaphore permits out of range: 0, use other configuration to disable a feature")?
878 : }),
879 CBC 1079 : "metric_collection_interval" => builder.metric_collection_interval(parse_toml_duration(key, item)?),
880 1063 : "cached_metric_collection_interval" => builder.cached_metric_collection_interval(parse_toml_duration(key, item)?),
881 1051 : "metric_collection_endpoint" => {
882 16 : let endpoint = parse_toml_string(key, item)?.parse().context("failed to parse metric_collection_endpoint")?;
883 16 : builder.metric_collection_endpoint(Some(endpoint));
884 : },
885 1035 : "synthetic_size_calculation_interval" =>
886 15 : builder.synthetic_size_calculation_interval(parse_toml_duration(key, item)?),
887 1020 : "test_remote_failures" => builder.test_remote_failures(parse_toml_u64(key, item)?),
888 920 : "disk_usage_based_eviction" => {
889 4 : tracing::info!("disk_usage_based_eviction: {:#?}", &item);
890 4 : builder.disk_usage_based_eviction(
891 4 : deserialize_from_item("disk_usage_based_eviction", item)
892 4 : .context("parse disk_usage_based_eviction")?
893 : )
894 : },
895 916 : "ondemand_download_behavior_treat_error_as_warn" => builder.ondemand_download_behavior_treat_error_as_warn(parse_toml_bool(key, item)?),
896 916 : "background_task_maximum_delay" => builder.background_task_maximum_delay(parse_toml_duration(key, item)?),
897 907 : "control_plane_api" => {
898 906 : let parsed = parse_toml_string(key, item)?;
899 906 : if parsed.is_empty() {
900 1 : builder.control_plane_api(None)
901 : } else {
902 905 : builder.control_plane_api(Some(parsed.parse().context("failed to parse control plane URL")?))
903 : }
904 : },
905 1 : "control_plane_api_token" => {
906 UBC 0 : let parsed = parse_toml_string(key, item)?;
907 0 : if parsed.is_empty() {
908 0 : builder.control_plane_api_token(None)
909 : } else {
910 0 : builder.control_plane_api_token(Some(parsed.into()))
911 : }
912 : },
913 CBC 1 : "control_plane_emergency_mode" => {
914 1 : builder.control_plane_emergency_mode(parse_toml_bool(key, item)?)
915 : },
916 UBC 0 : "heatmap_upload_concurrency" => {
917 0 : builder.heatmap_upload_concurrency(parse_toml_u64(key, item)? as usize)
918 : },
919 0 : "secondary_download_concurrency" => {
920 0 : builder.secondary_download_concurrency(parse_toml_u64(key, item)? as usize)
921 : },
922 0 : "ingest_batch_size" => builder.ingest_batch_size(parse_toml_u64(key, item)?),
923 0 : _ => bail!("unrecognized pageserver option '{key}'"),
924 : }
925 : }
926 :
927 CBC 916 : let mut conf = builder.build().context("invalid config")?;
928 :
929 915 : if conf.http_auth_type == AuthType::NeonJWT || conf.pg_auth_type == AuthType::NeonJWT {
930 22 : let auth_validation_public_key_path = conf
931 22 : .auth_validation_public_key_path
932 22 : .get_or_insert_with(|| workdir.join("auth_public_key.pem"));
933 22 : ensure!(
934 22 : auth_validation_public_key_path.exists(),
935 UBC 0 : format!(
936 0 : "Can't find auth_validation_public_key at '{auth_validation_public_key_path}'",
937 0 : )
938 : );
939 CBC 893 : }
940 :
941 915 : conf.default_tenant_conf = t_conf.merge(TenantConf::default());
942 915 :
943 915 : Ok(conf)
944 916 : }
945 :
946 : #[cfg(test)]
947 47 : pub fn test_repo_dir(test_name: &str) -> Utf8PathBuf {
948 47 : let test_output_dir = std::env::var("TEST_OUTPUT").unwrap_or("../tmp_check".into());
949 47 : Utf8PathBuf::from(format!("{test_output_dir}/test_{test_name}"))
950 47 : }
951 :
952 45 : pub fn dummy_conf(repo_dir: Utf8PathBuf) -> Self {
953 45 : let pg_distrib_dir = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../pg_install");
954 45 :
955 45 : PageServerConf {
956 45 : id: NodeId(0),
957 45 : wait_lsn_timeout: Duration::from_secs(60),
958 45 : wal_redo_timeout: Duration::from_secs(60),
959 45 : page_cache_size: defaults::DEFAULT_PAGE_CACHE_SIZE,
960 45 : max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS,
961 45 : listen_pg_addr: defaults::DEFAULT_PG_LISTEN_ADDR.to_string(),
962 45 : listen_http_addr: defaults::DEFAULT_HTTP_LISTEN_ADDR.to_string(),
963 45 : availability_zone: None,
964 45 : superuser: "cloud_admin".to_string(),
965 45 : workdir: repo_dir,
966 45 : pg_distrib_dir,
967 45 : http_auth_type: AuthType::Trust,
968 45 : pg_auth_type: AuthType::Trust,
969 45 : auth_validation_public_key_path: None,
970 45 : remote_storage_config: None,
971 45 : default_tenant_conf: TenantConf::default(),
972 45 : broker_endpoint: storage_broker::DEFAULT_ENDPOINT.parse().unwrap(),
973 45 : broker_keepalive_interval: Duration::from_secs(5000),
974 45 : log_format: LogFormat::from_str(defaults::DEFAULT_LOG_FORMAT).unwrap(),
975 45 : concurrent_tenant_warmup: ConfigurableSemaphore::new(
976 45 : NonZeroUsize::new(DEFAULT_CONCURRENT_TENANT_WARMUP)
977 45 : .expect("Invalid default constant"),
978 45 : ),
979 45 : concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::default(),
980 45 : eviction_task_immitated_concurrent_logical_size_queries: ConfigurableSemaphore::default(
981 45 : ),
982 45 : metric_collection_interval: Duration::from_secs(60),
983 45 : cached_metric_collection_interval: Duration::from_secs(60 * 60),
984 45 : metric_collection_endpoint: defaults::DEFAULT_METRIC_COLLECTION_ENDPOINT,
985 45 : synthetic_size_calculation_interval: Duration::from_secs(60),
986 45 : disk_usage_based_eviction: None,
987 45 : test_remote_failures: 0,
988 45 : ondemand_download_behavior_treat_error_as_warn: false,
989 45 : background_task_maximum_delay: Duration::ZERO,
990 45 : control_plane_api: None,
991 45 : control_plane_api_token: None,
992 45 : control_plane_emergency_mode: false,
993 45 : heatmap_upload_concurrency: defaults::DEFAULT_HEATMAP_UPLOAD_CONCURRENCY,
994 45 : secondary_download_concurrency: defaults::DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY,
995 45 : ingest_batch_size: defaults::DEFAULT_INGEST_BATCH_SIZE,
996 45 : }
997 45 : }
998 : }
999 :
1000 : // Helper functions to parse a toml Item
1001 :
1002 4616 : fn parse_toml_string(name: &str, item: &Item) -> Result<String> {
1003 4616 : let s = item
1004 4616 : .as_str()
1005 4616 : .with_context(|| format!("configure option {name} is not a string"))?;
1006 4616 : Ok(s.to_string())
1007 4616 : }
1008 :
1009 1031 : fn parse_toml_u64(name: &str, item: &Item) -> Result<u64> {
1010 : // A toml integer is signed, so it cannot represent the full range of an u64. That's OK
1011 : // for our use, though.
1012 1031 : let i: i64 = item
1013 1031 : .as_integer()
1014 1031 : .with_context(|| format!("configure option {name} is not an integer"))?;
1015 1031 : if i < 0 {
1016 UBC 0 : bail!("configure option {name} cannot be negative");
1017 CBC 1031 : }
1018 1031 : Ok(i as u64)
1019 1031 : }
1020 :
1021 1 : fn parse_toml_bool(name: &str, item: &Item) -> Result<bool> {
1022 1 : item.as_bool()
1023 1 : .with_context(|| format!("configure option {name} is not a bool"))
1024 1 : }
1025 :
1026 72 : fn parse_toml_duration(name: &str, item: &Item) -> Result<Duration> {
1027 72 : let s = item
1028 72 : .as_str()
1029 72 : .with_context(|| format!("configure option {name} is not a string"))?;
1030 :
1031 72 : Ok(humantime::parse_duration(s)?)
1032 72 : }
1033 :
1034 1812 : fn parse_toml_from_str<T>(name: &str, item: &Item) -> anyhow::Result<T>
1035 1812 : where
1036 1812 : T: FromStr,
1037 1812 : <T as FromStr>::Err: std::fmt::Display,
1038 1812 : {
1039 1812 : let v = item
1040 1812 : .as_str()
1041 1812 : .with_context(|| format!("configure option {name} is not a string"))?;
1042 1812 : T::from_str(v).map_err(|e| {
1043 UBC 0 : anyhow!(
1044 0 : "Failed to parse string as {parse_type} for configure option {name}: {e}",
1045 0 : parse_type = stringify!(T)
1046 0 : )
1047 CBC 1812 : })
1048 1812 : }
1049 :
1050 4 : fn deserialize_from_item<T>(name: &str, item: &Item) -> anyhow::Result<T>
1051 4 : where
1052 4 : T: serde::de::DeserializeOwned,
1053 4 : {
1054 : // ValueDeserializer::new is not public, so use the ValueDeserializer's documented way
1055 4 : let deserializer = match item.clone().into_value() {
1056 4 : Ok(value) => value.into_deserializer(),
1057 UBC 0 : Err(item) => anyhow::bail!("toml_edit::Item '{item}' is not a toml_edit::Value"),
1058 : };
1059 CBC 4 : T::deserialize(deserializer).with_context(|| format!("deserializing item for node {name}"))
1060 4 : }
1061 :
1062 : /// Configurable semaphore permits setting.
1063 : ///
1064 : /// Does not allow semaphore permits to be zero, because at runtime initially zero permits and empty
1065 : /// semaphore cannot be distinguished, leading any feature using these to await forever (or until
1066 : /// new permits are added).
1067 UBC 0 : #[derive(Debug, Clone)]
1068 : pub struct ConfigurableSemaphore {
1069 : initial_permits: NonZeroUsize,
1070 : inner: std::sync::Arc<tokio::sync::Semaphore>,
1071 : }
1072 :
1073 : impl ConfigurableSemaphore {
1074 : pub const DEFAULT_INITIAL: NonZeroUsize = match NonZeroUsize::new(1) {
1075 : Some(x) => x,
1076 : None => panic!("const unwrap is not yet stable"),
1077 : };
1078 :
1079 : /// Initializse using a non-zero amount of permits.
1080 : ///
1081 : /// Require a non-zero initial permits, because using permits == 0 is a crude way to disable a
1082 : /// feature such as [`Tenant::gather_size_inputs`]. Otherwise any semaphore using future will
1083 : /// behave like [`futures::future::pending`], just waiting until new permits are added.
1084 : ///
1085 : /// [`Tenant::gather_size_inputs`]: crate::tenant::Tenant::gather_size_inputs
1086 CBC 2886 : pub fn new(initial_permits: NonZeroUsize) -> Self {
1087 2886 : ConfigurableSemaphore {
1088 2886 : initial_permits,
1089 2886 : inner: std::sync::Arc::new(tokio::sync::Semaphore::new(initial_permits.get())),
1090 2886 : }
1091 2886 : }
1092 :
1093 : /// Returns the configured amount of permits.
1094 557 : pub fn initial_permits(&self) -> NonZeroUsize {
1095 557 : self.initial_permits
1096 557 : }
1097 : }
1098 :
1099 : impl Default for ConfigurableSemaphore {
1100 94 : fn default() -> Self {
1101 94 : Self::new(Self::DEFAULT_INITIAL)
1102 94 : }
1103 : }
1104 :
1105 : impl PartialEq for ConfigurableSemaphore {
1106 6 : fn eq(&self, other: &Self) -> bool {
1107 6 : // the number of permits can be increased at runtime, so we cannot really fulfill the
1108 6 : // PartialEq value equality otherwise
1109 6 : self.initial_permits == other.initial_permits
1110 6 : }
1111 : }
1112 :
1113 : impl Eq for ConfigurableSemaphore {}
1114 :
1115 : impl ConfigurableSemaphore {
1116 294 : pub fn inner(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
1117 294 : &self.inner
1118 294 : }
1119 : }
1120 :
1121 : #[cfg(test)]
1122 : mod tests {
1123 : use std::{
1124 : fs,
1125 : num::{NonZeroU32, NonZeroUsize},
1126 : };
1127 :
1128 : use camino_tempfile::{tempdir, Utf8TempDir};
1129 : use remote_storage::{RemoteStorageKind, S3Config};
1130 : use utils::serde_percent::Percent;
1131 :
1132 : use super::*;
1133 : use crate::{tenant::config::EvictionPolicy, DEFAULT_PG_VERSION};
1134 :
1135 : const ALL_BASE_VALUES_TOML: &str = r#"
1136 : # Initial configuration file created by 'pageserver --init'
1137 :
1138 : listen_pg_addr = '127.0.0.1:64000'
1139 : listen_http_addr = '127.0.0.1:9898'
1140 :
1141 : wait_lsn_timeout = '111 s'
1142 : wal_redo_timeout = '111 s'
1143 :
1144 : page_cache_size = 444
1145 : max_file_descriptors = 333
1146 :
1147 : # initial superuser role name to use when creating a new tenant
1148 : initial_superuser_name = 'zzzz'
1149 : id = 10
1150 :
1151 : metric_collection_interval = '222 s'
1152 : cached_metric_collection_interval = '22200 s'
1153 : metric_collection_endpoint = 'http://localhost:80/metrics'
1154 : synthetic_size_calculation_interval = '333 s'
1155 :
1156 : log_format = 'json'
1157 : background_task_maximum_delay = '334 s'
1158 :
1159 : "#;
1160 :
1161 1 : #[test]
1162 1 : fn parse_defaults() -> anyhow::Result<()> {
1163 1 : let tempdir = tempdir()?;
1164 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1165 1 : let broker_endpoint = storage_broker::DEFAULT_ENDPOINT;
1166 1 : // we have to create dummy values to overcome the validation errors
1167 1 : let config_string = format!(
1168 1 : "pg_distrib_dir='{pg_distrib_dir}'\nid=10\nbroker_endpoint = '{broker_endpoint}'",
1169 1 : );
1170 1 : let toml = config_string.parse()?;
1171 :
1172 1 : let parsed_config = PageServerConf::parse_and_validate(&toml, &workdir)
1173 1 : .unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
1174 1 :
1175 1 : assert_eq!(
1176 1 : parsed_config,
1177 1 : PageServerConf {
1178 1 : id: NodeId(10),
1179 1 : listen_pg_addr: defaults::DEFAULT_PG_LISTEN_ADDR.to_string(),
1180 1 : listen_http_addr: defaults::DEFAULT_HTTP_LISTEN_ADDR.to_string(),
1181 1 : availability_zone: None,
1182 1 : wait_lsn_timeout: humantime::parse_duration(defaults::DEFAULT_WAIT_LSN_TIMEOUT)?,
1183 1 : wal_redo_timeout: humantime::parse_duration(defaults::DEFAULT_WAL_REDO_TIMEOUT)?,
1184 1 : superuser: defaults::DEFAULT_SUPERUSER.to_string(),
1185 1 : page_cache_size: defaults::DEFAULT_PAGE_CACHE_SIZE,
1186 1 : max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS,
1187 1 : workdir,
1188 1 : pg_distrib_dir,
1189 1 : http_auth_type: AuthType::Trust,
1190 1 : pg_auth_type: AuthType::Trust,
1191 1 : auth_validation_public_key_path: None,
1192 1 : remote_storage_config: None,
1193 1 : default_tenant_conf: TenantConf::default(),
1194 1 : broker_endpoint: storage_broker::DEFAULT_ENDPOINT.parse().unwrap(),
1195 1 : broker_keepalive_interval: humantime::parse_duration(
1196 1 : storage_broker::DEFAULT_KEEPALIVE_INTERVAL
1197 1 : )?,
1198 1 : log_format: LogFormat::from_str(defaults::DEFAULT_LOG_FORMAT).unwrap(),
1199 1 : concurrent_tenant_warmup: ConfigurableSemaphore::new(
1200 1 : NonZeroUsize::new(DEFAULT_CONCURRENT_TENANT_WARMUP).unwrap()
1201 1 : ),
1202 1 : concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::default(),
1203 1 : eviction_task_immitated_concurrent_logical_size_queries:
1204 1 : ConfigurableSemaphore::default(),
1205 1 : metric_collection_interval: humantime::parse_duration(
1206 1 : defaults::DEFAULT_METRIC_COLLECTION_INTERVAL
1207 1 : )?,
1208 1 : cached_metric_collection_interval: humantime::parse_duration(
1209 1 : defaults::DEFAULT_CACHED_METRIC_COLLECTION_INTERVAL
1210 1 : )?,
1211 1 : metric_collection_endpoint: defaults::DEFAULT_METRIC_COLLECTION_ENDPOINT,
1212 1 : synthetic_size_calculation_interval: humantime::parse_duration(
1213 1 : defaults::DEFAULT_SYNTHETIC_SIZE_CALCULATION_INTERVAL
1214 1 : )?,
1215 1 : disk_usage_based_eviction: None,
1216 1 : test_remote_failures: 0,
1217 1 : ondemand_download_behavior_treat_error_as_warn: false,
1218 1 : background_task_maximum_delay: humantime::parse_duration(
1219 1 : defaults::DEFAULT_BACKGROUND_TASK_MAXIMUM_DELAY
1220 1 : )?,
1221 1 : control_plane_api: None,
1222 1 : control_plane_api_token: None,
1223 : control_plane_emergency_mode: false,
1224 : heatmap_upload_concurrency: defaults::DEFAULT_HEATMAP_UPLOAD_CONCURRENCY,
1225 : secondary_download_concurrency: defaults::DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY,
1226 : ingest_batch_size: defaults::DEFAULT_INGEST_BATCH_SIZE,
1227 : },
1228 UBC 0 : "Correct defaults should be used when no config values are provided"
1229 : );
1230 :
1231 CBC 1 : Ok(())
1232 1 : }
1233 :
1234 1 : #[test]
1235 1 : fn parse_basic_config() -> anyhow::Result<()> {
1236 1 : let tempdir = tempdir()?;
1237 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1238 1 : let broker_endpoint = storage_broker::DEFAULT_ENDPOINT;
1239 1 :
1240 1 : let config_string = format!(
1241 1 : "{ALL_BASE_VALUES_TOML}pg_distrib_dir='{pg_distrib_dir}'\nbroker_endpoint = '{broker_endpoint}'",
1242 1 : );
1243 1 : let toml = config_string.parse()?;
1244 :
1245 1 : let parsed_config = PageServerConf::parse_and_validate(&toml, &workdir)
1246 1 : .unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
1247 1 :
1248 1 : assert_eq!(
1249 1 : parsed_config,
1250 1 : PageServerConf {
1251 1 : id: NodeId(10),
1252 1 : listen_pg_addr: "127.0.0.1:64000".to_string(),
1253 1 : listen_http_addr: "127.0.0.1:9898".to_string(),
1254 1 : availability_zone: None,
1255 1 : wait_lsn_timeout: Duration::from_secs(111),
1256 1 : wal_redo_timeout: Duration::from_secs(111),
1257 1 : superuser: "zzzz".to_string(),
1258 1 : page_cache_size: 444,
1259 1 : max_file_descriptors: 333,
1260 1 : workdir,
1261 1 : pg_distrib_dir,
1262 1 : http_auth_type: AuthType::Trust,
1263 1 : pg_auth_type: AuthType::Trust,
1264 1 : auth_validation_public_key_path: None,
1265 1 : remote_storage_config: None,
1266 1 : default_tenant_conf: TenantConf::default(),
1267 1 : broker_endpoint: storage_broker::DEFAULT_ENDPOINT.parse().unwrap(),
1268 1 : broker_keepalive_interval: Duration::from_secs(5),
1269 1 : log_format: LogFormat::Json,
1270 1 : concurrent_tenant_warmup: ConfigurableSemaphore::new(
1271 1 : NonZeroUsize::new(DEFAULT_CONCURRENT_TENANT_WARMUP).unwrap()
1272 1 : ),
1273 1 : concurrent_tenant_size_logical_size_queries: ConfigurableSemaphore::default(),
1274 1 : eviction_task_immitated_concurrent_logical_size_queries:
1275 1 : ConfigurableSemaphore::default(),
1276 1 : metric_collection_interval: Duration::from_secs(222),
1277 1 : cached_metric_collection_interval: Duration::from_secs(22200),
1278 1 : metric_collection_endpoint: Some(Url::parse("http://localhost:80/metrics")?),
1279 1 : synthetic_size_calculation_interval: Duration::from_secs(333),
1280 1 : disk_usage_based_eviction: None,
1281 1 : test_remote_failures: 0,
1282 1 : ondemand_download_behavior_treat_error_as_warn: false,
1283 1 : background_task_maximum_delay: Duration::from_secs(334),
1284 1 : control_plane_api: None,
1285 1 : control_plane_api_token: None,
1286 : control_plane_emergency_mode: false,
1287 : heatmap_upload_concurrency: defaults::DEFAULT_HEATMAP_UPLOAD_CONCURRENCY,
1288 : secondary_download_concurrency: defaults::DEFAULT_SECONDARY_DOWNLOAD_CONCURRENCY,
1289 : ingest_batch_size: 100,
1290 : },
1291 UBC 0 : "Should be able to parse all basic config values correctly"
1292 : );
1293 :
1294 CBC 1 : Ok(())
1295 1 : }
1296 :
1297 1 : #[test]
1298 1 : fn parse_remote_fs_storage_config() -> anyhow::Result<()> {
1299 1 : let tempdir = tempdir()?;
1300 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1301 1 : let broker_endpoint = "http://127.0.0.1:7777";
1302 1 :
1303 1 : let local_storage_path = tempdir.path().join("local_remote_storage");
1304 1 :
1305 1 : let identical_toml_declarations = &[
1306 1 : format!(
1307 1 : r#"[remote_storage]
1308 1 : local_path = '{local_storage_path}'"#,
1309 1 : ),
1310 1 : format!("remote_storage={{local_path='{local_storage_path}'}}"),
1311 1 : ];
1312 :
1313 3 : for remote_storage_config_str in identical_toml_declarations {
1314 2 : let config_string = format!(
1315 2 : r#"{ALL_BASE_VALUES_TOML}
1316 2 : pg_distrib_dir='{pg_distrib_dir}'
1317 2 : broker_endpoint = '{broker_endpoint}'
1318 2 :
1319 2 : {remote_storage_config_str}"#,
1320 2 : );
1321 :
1322 2 : let toml = config_string.parse()?;
1323 :
1324 2 : let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir)
1325 2 : .unwrap_or_else(|e| {
1326 UBC 0 : panic!("Failed to parse config '{config_string}', reason: {e:?}")
1327 CBC 2 : })
1328 2 : .remote_storage_config
1329 2 : .expect("Should have remote storage config for the local FS");
1330 2 :
1331 2 : assert_eq!(
1332 2 : parsed_remote_storage_config,
1333 2 : RemoteStorageConfig {
1334 2 : storage: RemoteStorageKind::LocalFs(local_storage_path.clone()),
1335 2 : },
1336 UBC 0 : "Remote storage config should correctly parse the local FS config and fill other storage defaults"
1337 : );
1338 : }
1339 CBC 1 : Ok(())
1340 1 : }
1341 :
1342 1 : #[test]
1343 1 : fn parse_remote_s3_storage_config() -> anyhow::Result<()> {
1344 1 : let tempdir = tempdir()?;
1345 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1346 :
1347 1 : let bucket_name = "some-sample-bucket".to_string();
1348 1 : let bucket_region = "eu-north-1".to_string();
1349 1 : let prefix_in_bucket = "test_prefix".to_string();
1350 1 : let endpoint = "http://localhost:5000".to_string();
1351 1 : let max_concurrent_syncs = NonZeroUsize::new(111).unwrap();
1352 1 : let max_sync_errors = NonZeroU32::new(222).unwrap();
1353 1 : let s3_concurrency_limit = NonZeroUsize::new(333).unwrap();
1354 1 : let broker_endpoint = "http://127.0.0.1:7777";
1355 1 :
1356 1 : let identical_toml_declarations = &[
1357 1 : format!(
1358 1 : r#"[remote_storage]
1359 1 : max_concurrent_syncs = {max_concurrent_syncs}
1360 1 : max_sync_errors = {max_sync_errors}
1361 1 : bucket_name = '{bucket_name}'
1362 1 : bucket_region = '{bucket_region}'
1363 1 : prefix_in_bucket = '{prefix_in_bucket}'
1364 1 : endpoint = '{endpoint}'
1365 1 : concurrency_limit = {s3_concurrency_limit}"#
1366 1 : ),
1367 1 : format!(
1368 1 : "remote_storage={{max_concurrent_syncs={max_concurrent_syncs}, max_sync_errors={max_sync_errors}, bucket_name='{bucket_name}',\
1369 1 : bucket_region='{bucket_region}', prefix_in_bucket='{prefix_in_bucket}', endpoint='{endpoint}', concurrency_limit={s3_concurrency_limit}}}",
1370 1 : ),
1371 1 : ];
1372 :
1373 3 : for remote_storage_config_str in identical_toml_declarations {
1374 2 : let config_string = format!(
1375 2 : r#"{ALL_BASE_VALUES_TOML}
1376 2 : pg_distrib_dir='{pg_distrib_dir}'
1377 2 : broker_endpoint = '{broker_endpoint}'
1378 2 :
1379 2 : {remote_storage_config_str}"#,
1380 2 : );
1381 :
1382 2 : let toml = config_string.parse()?;
1383 :
1384 2 : let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir)
1385 2 : .unwrap_or_else(|e| {
1386 UBC 0 : panic!("Failed to parse config '{config_string}', reason: {e:?}")
1387 CBC 2 : })
1388 2 : .remote_storage_config
1389 2 : .expect("Should have remote storage config for S3");
1390 2 :
1391 2 : assert_eq!(
1392 2 : parsed_remote_storage_config,
1393 2 : RemoteStorageConfig {
1394 2 : storage: RemoteStorageKind::AwsS3(S3Config {
1395 2 : bucket_name: bucket_name.clone(),
1396 2 : bucket_region: bucket_region.clone(),
1397 2 : prefix_in_bucket: Some(prefix_in_bucket.clone()),
1398 2 : endpoint: Some(endpoint.clone()),
1399 2 : concurrency_limit: s3_concurrency_limit,
1400 2 : max_keys_per_list_response: None,
1401 2 : }),
1402 2 : },
1403 UBC 0 : "Remote storage config should correctly parse the S3 config"
1404 : );
1405 : }
1406 CBC 1 : Ok(())
1407 1 : }
1408 :
1409 1 : #[test]
1410 1 : fn parse_tenant_config() -> anyhow::Result<()> {
1411 1 : let tempdir = tempdir()?;
1412 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1413 :
1414 1 : let broker_endpoint = "http://127.0.0.1:7777";
1415 1 : let trace_read_requests = true;
1416 1 :
1417 1 : let config_string = format!(
1418 1 : r#"{ALL_BASE_VALUES_TOML}
1419 1 : pg_distrib_dir='{pg_distrib_dir}'
1420 1 : broker_endpoint = '{broker_endpoint}'
1421 1 :
1422 1 : [tenant_config]
1423 1 : trace_read_requests = {trace_read_requests}"#,
1424 1 : );
1425 :
1426 1 : let toml = config_string.parse()?;
1427 :
1428 1 : let conf = PageServerConf::parse_and_validate(&toml, &workdir)?;
1429 1 : assert_eq!(
1430 : conf.default_tenant_conf.trace_read_requests, trace_read_requests,
1431 UBC 0 : "Tenant config from pageserver config file should be parsed and udpated values used as defaults for all tenants",
1432 : );
1433 :
1434 CBC 1 : Ok(())
1435 1 : }
1436 :
1437 1 : #[test]
1438 1 : fn parse_incorrect_tenant_config() -> anyhow::Result<()> {
1439 1 : let config_string = r#"
1440 1 : [tenant_config]
1441 1 : checkpoint_distance = -1 # supposed to be an u64
1442 1 : "#
1443 1 : .to_string();
1444 :
1445 1 : let toml: Document = config_string.parse()?;
1446 1 : let item = toml.get("tenant_config").unwrap();
1447 1 : let error = TenantConfOpt::try_from(item.to_owned()).unwrap_err();
1448 1 :
1449 1 : let expected_error_str = "checkpoint_distance: invalid value: integer `-1`, expected u64";
1450 1 : assert_eq!(error.to_string(), expected_error_str);
1451 :
1452 1 : Ok(())
1453 1 : }
1454 :
1455 1 : #[test]
1456 1 : fn parse_override_tenant_config() -> anyhow::Result<()> {
1457 1 : let config_string = r#"tenant_config={ min_resident_size_override = 400 }"#.to_string();
1458 :
1459 1 : let toml: Document = config_string.parse()?;
1460 1 : let item = toml.get("tenant_config").unwrap();
1461 1 : let conf = TenantConfOpt::try_from(item.to_owned()).unwrap();
1462 1 :
1463 1 : assert_eq!(conf.min_resident_size_override, Some(400));
1464 :
1465 1 : Ok(())
1466 1 : }
1467 :
1468 1 : #[test]
1469 1 : fn eviction_pageserver_config_parse() -> anyhow::Result<()> {
1470 1 : let tempdir = tempdir()?;
1471 1 : let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?;
1472 :
1473 1 : let pageserver_conf_toml = format!(
1474 1 : r#"pg_distrib_dir = "{pg_distrib_dir}"
1475 1 : metric_collection_endpoint = "http://sample.url"
1476 1 : metric_collection_interval = "10min"
1477 1 : id = 222
1478 1 :
1479 1 : [disk_usage_based_eviction]
1480 1 : max_usage_pct = 80
1481 1 : min_avail_bytes = 0
1482 1 : period = "10s"
1483 1 :
1484 1 : [tenant_config]
1485 1 : evictions_low_residence_duration_metric_threshold = "20m"
1486 1 :
1487 1 : [tenant_config.eviction_policy]
1488 1 : kind = "LayerAccessThreshold"
1489 1 : period = "20m"
1490 1 : threshold = "20m"
1491 1 : "#,
1492 1 : );
1493 1 : let toml: Document = pageserver_conf_toml.parse()?;
1494 1 : let conf = PageServerConf::parse_and_validate(&toml, &workdir)?;
1495 :
1496 1 : assert_eq!(conf.pg_distrib_dir, pg_distrib_dir);
1497 1 : assert_eq!(
1498 1 : conf.metric_collection_endpoint,
1499 1 : Some("http://sample.url".parse().unwrap())
1500 1 : );
1501 1 : assert_eq!(
1502 1 : conf.metric_collection_interval,
1503 1 : Duration::from_secs(10 * 60)
1504 1 : );
1505 1 : assert_eq!(
1506 1 : conf.default_tenant_conf
1507 1 : .evictions_low_residence_duration_metric_threshold,
1508 1 : Duration::from_secs(20 * 60)
1509 1 : );
1510 1 : assert_eq!(conf.id, NodeId(222));
1511 1 : assert_eq!(
1512 1 : conf.disk_usage_based_eviction,
1513 1 : Some(DiskUsageEvictionTaskConfig {
1514 1 : max_usage_pct: Percent::new(80).unwrap(),
1515 1 : min_avail_bytes: 0,
1516 1 : period: Duration::from_secs(10),
1517 1 : #[cfg(feature = "testing")]
1518 1 : mock_statvfs: None,
1519 1 : eviction_order: crate::disk_usage_eviction_task::EvictionOrder::AbsoluteAccessed,
1520 1 : })
1521 1 : );
1522 1 : match &conf.default_tenant_conf.eviction_policy {
1523 UBC 0 : EvictionPolicy::NoEviction => panic!("Unexpected eviction opolicy tenant settings"),
1524 CBC 1 : EvictionPolicy::LayerAccessThreshold(eviction_thresold) => {
1525 1 : assert_eq!(eviction_thresold.period, Duration::from_secs(20 * 60));
1526 1 : assert_eq!(eviction_thresold.threshold, Duration::from_secs(20 * 60));
1527 : }
1528 : }
1529 :
1530 1 : Ok(())
1531 1 : }
1532 :
1533 6 : fn prepare_fs(tempdir: &Utf8TempDir) -> anyhow::Result<(Utf8PathBuf, Utf8PathBuf)> {
1534 6 : let tempdir_path = tempdir.path();
1535 6 :
1536 6 : let workdir = tempdir_path.join("workdir");
1537 6 : fs::create_dir_all(&workdir)?;
1538 :
1539 6 : let pg_distrib_dir = tempdir_path.join("pg_distrib");
1540 6 : let pg_distrib_dir_versioned = pg_distrib_dir.join(format!("v{DEFAULT_PG_VERSION}"));
1541 6 : fs::create_dir_all(&pg_distrib_dir_versioned)?;
1542 6 : let postgres_bin_dir = pg_distrib_dir_versioned.join("bin");
1543 6 : fs::create_dir_all(&postgres_bin_dir)?;
1544 6 : fs::write(postgres_bin_dir.join("postgres"), "I'm postgres, trust me")?;
1545 :
1546 6 : Ok((workdir, pg_distrib_dir))
1547 6 : }
1548 : }
|