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