From 114a757d1c8cb638a42ceb455f47b7e6dbf30068 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 Nov 2021 12:39:06 +0200 Subject: [PATCH] Use generic config parameters in pageserver cli Co-authored-by: Heikki Linnakangas --- Cargo.lock | 49 +- control_plane/safekeepers.conf | 4 +- control_plane/simple.conf | 4 +- control_plane/src/local_env.rs | 66 +- control_plane/src/storage.rs | 74 +- docker-entrypoint.sh | 2 +- docs/settings.md | 128 ++-- pageserver/Cargo.toml | 2 +- pageserver/README.md | 4 +- pageserver/src/bin/pageserver.rs | 614 ++-------------- pageserver/src/branches.rs | 2 +- pageserver/src/config.rs | 662 ++++++++++++++++++ pageserver/src/http/routes.rs | 2 +- pageserver/src/import_datadir.rs | 2 +- pageserver/src/layered_repository.rs | 2 +- .../src/layered_repository/delta_layer.rs | 2 +- .../src/layered_repository/ephemeral_file.rs | 2 +- pageserver/src/layered_repository/filename.rs | 2 +- .../src/layered_repository/image_layer.rs | 2 +- .../src/layered_repository/inmemory_layer.rs | 2 +- .../src/layered_repository/layer_map.rs | 2 +- pageserver/src/layered_repository/metadata.rs | 2 +- .../src/layered_repository/page_versions.rs | 2 +- pageserver/src/lib.rs | 200 +----- pageserver/src/page_cache.rs | 2 +- pageserver/src/page_service.rs | 2 +- pageserver/src/remote_storage.rs | 2 +- pageserver/src/remote_storage/rust_s3.rs | 2 +- pageserver/src/remote_storage/storage_sync.rs | 4 +- .../remote_storage/storage_sync/download.rs | 2 +- .../src/remote_storage/storage_sync/index.rs | 2 +- .../src/remote_storage/storage_sync/upload.rs | 2 +- pageserver/src/repository.rs | 2 +- pageserver/src/tenant_mgr.rs | 2 +- pageserver/src/tenant_threads.rs | 2 +- pageserver/src/virtual_file.rs | 4 +- pageserver/src/walreceiver.rs | 2 +- pageserver/src/walredo.rs | 2 +- test_runner/fixtures/zenith_fixtures.py | 4 +- zenith/src/main.rs | 14 +- zenith_utils/src/postgres_backend.rs | 2 +- 41 files changed, 982 insertions(+), 903 deletions(-) create mode 100644 pageserver/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index c4e130c3ac..d2524fdc9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,16 @@ dependencies = [ "vec_map", ] +[[package]] +name = "combine" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const_format" version = "0.2.22" @@ -447,6 +457,12 @@ dependencies = [ "rand", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encoding_rs" version = "0.8.28" @@ -856,6 +872,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -894,6 +919,15 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kstring" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b310ccceade8121d7d77fee406160e457c2f4e7c7982d589da3499bc7ea4526" +dependencies = [ + "serde", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1162,7 +1196,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml", + "toml_edit", "tracing", "tracing-futures", "url", @@ -2096,6 +2130,19 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c14e16aafed5fb7a1bdc293270ca28f17d0a09b4f528f8de34521f2a32d99198" +dependencies = [ + "combine", + "indexmap", + "itertools", + "kstring", + "serde", +] + [[package]] name = "tower-service" version = "0.3.1" diff --git a/control_plane/safekeepers.conf b/control_plane/safekeepers.conf index 99f4545990..118432556b 100644 --- a/control_plane/safekeepers.conf +++ b/control_plane/safekeepers.conf @@ -1,7 +1,7 @@ # Page server and three safekeepers. [pageserver] -pg_port = 64000 -http_port = 9898 +listen_pg_addr = 'localhost:64000' +listen_http_addr = 'localhost:9898' auth_type = 'Trust' [[safekeepers]] diff --git a/control_plane/simple.conf b/control_plane/simple.conf index d482e111c1..3eac5d5757 100644 --- a/control_plane/simple.conf +++ b/control_plane/simple.conf @@ -1,8 +1,8 @@ # Minimal zenith environment with one safekeeper. This is equivalent to the built-in # defaults that you get with no --config [pageserver] -pg_port = 64000 -http_port = 9898 +listen_pg_addr = 'localhost:64000' +listen_http_addr = 'localhost:9898' auth_type = 'Trust' [[safekeepers]] diff --git a/control_plane/src/local_env.rs b/control_plane/src/local_env.rs index dad6af0646..376d78a9cb 100644 --- a/control_plane/src/local_env.rs +++ b/control_plane/src/local_env.rs @@ -1,10 +1,9 @@ -// -// This module is responsible for locating and loading paths in a local setup. -// -// Now it also provides init method which acts like a stub for proper installation -// script which will use local paths. -// -use anyhow::{Context, Result}; +//! This module is responsible for locating and loading paths in a local setup. +//! +//! Now it also provides init method which acts like a stub for proper installation +//! script which will use local paths. + +use anyhow::{bail, Context}; use serde::{Deserialize, Serialize}; use std::env; use std::fmt::Write; @@ -64,8 +63,8 @@ pub struct LocalEnv { #[serde(default)] pub struct PageServerConf { // Pageserver connection settings - pub pg_port: u16, - pub http_port: u16, + pub listen_pg_addr: String, + pub listen_http_addr: String, // used to determine which auth type is used pub auth_type: AuthType, @@ -77,10 +76,10 @@ pub struct PageServerConf { impl Default for PageServerConf { fn default() -> Self { Self { - pg_port: 0, - http_port: 0, + listen_pg_addr: String::new(), + listen_http_addr: String::new(), auth_type: AuthType::Trust, - auth_token: "".to_string(), + auth_token: String::new(), } } } @@ -97,7 +96,7 @@ pub struct SafekeeperConf { impl Default for SafekeeperConf { fn default() -> Self { Self { - name: "".to_string(), + name: String::new(), pg_port: 0, http_port: 0, sync: true, @@ -114,11 +113,11 @@ impl LocalEnv { self.pg_distrib_dir.join("lib") } - pub fn pageserver_bin(&self) -> Result { + pub fn pageserver_bin(&self) -> anyhow::Result { Ok(self.zenith_distrib_dir.join("pageserver")) } - pub fn safekeeper_bin(&self) -> Result { + pub fn safekeeper_bin(&self) -> anyhow::Result { Ok(self.zenith_distrib_dir.join("safekeeper")) } @@ -145,7 +144,7 @@ impl LocalEnv { /// /// Unlike 'load_config', this function fills in any defaults that are missing /// from the config file. - pub fn create_config(toml: &str) -> Result { + pub fn create_config(toml: &str) -> anyhow::Result { let mut env: LocalEnv = toml::from_str(toml)?; // Find postgres binaries. @@ -159,7 +158,7 @@ impl LocalEnv { } } if !env.pg_distrib_dir.join("bin/postgres").exists() { - anyhow::bail!( + bail!( "Can't find postgres binary at {}", env.pg_distrib_dir.display() ); @@ -169,11 +168,14 @@ impl LocalEnv { if env.zenith_distrib_dir == Path::new("") { env.zenith_distrib_dir = env::current_exe()?.parent().unwrap().to_owned(); } - if !env.zenith_distrib_dir.join("pageserver").exists() { - anyhow::bail!("Can't find pageserver binary."); - } - if !env.zenith_distrib_dir.join("safekeeper").exists() { - anyhow::bail!("Can't find safekeeper binary."); + for binary in ["pageserver", "safekeeper"] { + if !env.zenith_distrib_dir.join(binary).exists() { + bail!( + "Can't find binary '{}' in zenith distrib dir '{}'", + binary, + env.zenith_distrib_dir.display() + ); + } } // If no initial tenant ID was given, generate it. @@ -187,11 +189,11 @@ impl LocalEnv { } /// Locate and load config - pub fn load_config() -> Result { + pub fn load_config() -> anyhow::Result { let repopath = base_path(); if !repopath.exists() { - anyhow::bail!( + bail!( "Zenith config is not found in {}. You need to run 'zenith init' first", repopath.to_str().unwrap() ); @@ -209,7 +211,7 @@ impl LocalEnv { } // this function is used only for testing purposes in CLI e g generate tokens during init - pub fn generate_auth_token(&self, claims: &Claims) -> Result { + pub fn generate_auth_token(&self, claims: &Claims) -> anyhow::Result { let private_key_path = if self.private_key_path.is_absolute() { self.private_key_path.to_path_buf() } else { @@ -223,14 +225,14 @@ impl LocalEnv { // // Initialize a new Zenith repository // - pub fn init(&mut self) -> Result<()> { + pub fn init(&mut self) -> anyhow::Result<()> { // check if config already exists let base_path = &self.base_data_dir; if base_path == Path::new("") { - anyhow::bail!("repository base path is missing"); + bail!("repository base path is missing"); } if base_path.exists() { - anyhow::bail!( + bail!( "directory '{}' already exists. Perhaps already initialized?", base_path.to_str().unwrap() ); @@ -251,12 +253,12 @@ impl LocalEnv { .output() .with_context(|| "failed to generate auth private key")?; if !keygen_output.status.success() { - anyhow::bail!( + bail!( "openssl failed: '{}'", String::from_utf8_lossy(&keygen_output.stderr) ); } - self.private_key_path = Path::new("auth_private_key.pem").to_path_buf(); + self.private_key_path = PathBuf::from("auth_private_key.pem"); let public_key_path = base_path.join("auth_public_key.pem"); // openssl rsa -in private_key.pem -pubout -outform PEM -out public_key.pem @@ -270,7 +272,7 @@ impl LocalEnv { .output() .with_context(|| "failed to generate auth private key")?; if !keygen_output.status.success() { - anyhow::bail!( + bail!( "openssl failed: '{}'", String::from_utf8_lossy(&keygen_output.stderr) ); @@ -282,7 +284,7 @@ impl LocalEnv { fs::create_dir_all(self.pg_data_dirs_path())?; - for safekeeper in self.safekeepers.iter() { + for safekeeper in &self.safekeepers { fs::create_dir_all(self.safekeeper_data_dir(&safekeeper.name))?; } diff --git a/control_plane/src/storage.rs b/control_plane/src/storage.rs index b8118d7f4b..d1f6e421d0 100644 --- a/control_plane/src/storage.rs +++ b/control_plane/src/storage.rs @@ -78,58 +78,68 @@ impl PageServerNode { "" }; - PageServerNode { + Self { pg_connection_config: Self::pageserver_connection_config( password, - env.pageserver.pg_port, + &env.pageserver.listen_pg_addr, ), env: env.clone(), http_client: Client::new(), - http_base_url: format!("http://localhost:{}/v1", env.pageserver.http_port), + http_base_url: format!("http://{}/v1", env.pageserver.listen_http_addr), } } /// Construct libpq connection string for connecting to the pageserver. - fn pageserver_connection_config(password: &str, port: u16) -> Config { - format!("postgresql://no_user:{}@localhost:{}/no_db", password, port) + fn pageserver_connection_config(password: &str, listen_addr: &str) -> Config { + format!("postgresql://no_user:{}@{}/no_db", password, listen_addr) .parse() .unwrap() } pub fn init(&self, create_tenant: Option<&str>) -> anyhow::Result<()> { - let listen_pg = format!("localhost:{}", self.env.pageserver.pg_port); - let listen_http = format!("localhost:{}", self.env.pageserver.http_port); - let mut args = vec![ - "--init", - "-D", - self.env.base_data_dir.to_str().unwrap(), - "--postgres-distrib", - self.env.pg_distrib_dir.to_str().unwrap(), - "--listen-pg", - &listen_pg, - "--listen-http", - &listen_http, - ]; - - let auth_type_str = &self.env.pageserver.auth_type.to_string(); - if self.env.pageserver.auth_type != AuthType::Trust { - args.extend(&["--auth-validation-public-key-path", "auth_public_key.pem"]); - } - args.extend(&["--auth-type", auth_type_str]); - - if let Some(tenantid) = create_tenant { - args.extend(&["--create-tenant", tenantid]) - } - let mut cmd = Command::new(self.env.pageserver_bin()?); - cmd.args(args).env_clear().env("RUST_BACKTRACE", "1"); - let var = "LLVM_PROFILE_FILE"; if let Some(val) = std::env::var_os(var) { cmd.env(var, val); } - if !cmd.status()?.success() { + // FIXME: the paths should be shell-escaped to handle paths with spaces, quotas etc. + let mut args = vec![ + "--init".to_string(), + "-D".to_string(), + self.env.base_data_dir.display().to_string(), + "-c".to_string(), + format!("pg_distrib_dir='{}'", self.env.pg_distrib_dir.display()), + "-c".to_string(), + format!("auth_type='{}'", self.env.pageserver.auth_type), + "-c".to_string(), + format!( + "listen_http_addr='{}'", + self.env.pageserver.listen_http_addr + ), + "-c".to_string(), + format!("listen_pg_addr='{}'", self.env.pageserver.listen_pg_addr), + ]; + + if self.env.pageserver.auth_type != AuthType::Trust { + args.extend([ + "-c".to_string(), + "auth_validation_public_key_path='auth_public_key.pem'".to_string(), + ]); + } + + if let Some(tenantid) = create_tenant { + args.extend(["--create-tenant".to_string(), tenantid.to_string()]) + } + + let status = cmd + .args(args) + .env_clear() + .env("RUST_BACKTRACE", "1") + .status() + .expect("pageserver init failed"); + + if !status.success() { bail!("pageserver init failed"); } diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 566e77c1a4..aa61575ba5 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -4,7 +4,7 @@ set -eux if [ "$1" = 'pageserver' ]; then if [ ! -d "/data/tenants" ]; then echo "Initializing pageserver data directory" - pageserver --init -D /data --postgres-distrib /usr/local + pageserver --init -D /data -c "pg_distrib_dir='/usr/local'" fi echo "Staring pageserver at 0.0.0.0:6400" pageserver -l 0.0.0.0:6400 --listen-http 0.0.0.0:9898 -D /data diff --git a/docs/settings.md b/docs/settings.md index f15044e575..4293a05edd 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -1,16 +1,53 @@ ## Pageserver -### listen_pg_addr +Pageserver is mainly configured via a `pageserver.toml` config file. +If there's no such file during `init` phase of the server, it creates the file itself. Without 'init', the file is read. -Network interface and port number to listen at for connections from -the compute nodes and safekeepers. The default is `127.0.0.1:64000`. +There's a possibility to pass an arbitrary config value to the pageserver binary as an argument: such values override +the values in the config file, if any are specified for the same key and get into the final config during init phase. -### listen_http_addr -Network interface and port number to listen at for admin connections. -The default is `127.0.0.1:9898`. +### Config example -### checkpoint_distance +```toml +# Initial configuration file created by 'pageserver --init' + +listen_pg_addr = '127.0.0.1:64000' +listen_http_addr = '127.0.0.1:9898' + +checkpoint_distance = '268435456' # in bytes +checkpoint_period = '1 s' + +gc_period = '100 s' +gc_horizon = '67108864' + +max_file_descriptors = '100' + +# initial superuser role name to use when creating a new tenant +initial_superuser_name = 'zenith_admin' + +# [remote_storage] +``` + +The config above shows default values for all basic pageserver settings. +Pageserver uses default values for all files that are missing in the config, so it's not a hard error to leave the config blank. +Yet, it validates the config values it can (e.g. postgres install dir) and errors if the validation fails, refusing to start. + +Note the `[remote_storage]` section: it's a [table](https://toml.io/en/v1.0.0#table) in TOML specification and + +* either has to be placed in the config after the table-less values such as `initial_superuser_name = 'zenith_admin'` + +* or can be placed anywhere if rewritten in identical form as [inline table](https://toml.io/en/v1.0.0#inline-table): `remote_storage = {foo = 2}` + +### Config values + +All values can be passed as an argument to the pageserver binary, using the `-c` parameter and specified as a valid TOML string. All tables should be passed in the inline form. + +Example: `${PAGESERVER_BIN} -c "checkpoint_period = '100 s'" -c "remote_storage={local_path='/some/local/path/'}"` + +Note that TOML distinguishes between strings and integers, the former require single or double quotes around them. + +#### checkpoint_distance `checkpoint_distance` is the amount of incoming WAL that is held in the open layer, before it's flushed to local disk. It puts an upper @@ -31,97 +68,108 @@ S3. The unit is # of bytes. -### checkpoint_period +#### checkpoint_period The pageserver checks whether `checkpoint_distance` has been reached every `checkpoint_period` seconds. Default is 1 s, which should be fine. -### gc_horizon +#### gc_horizon `gz_horizon` determines how much history is retained, to allow branching and read replicas at an older point in time. The unit is # of bytes of WAL. Page versions older than this are garbage collected away. -### gc_period +#### gc_period Interval at which garbage collection is triggered. Default is 100 s. -### superuser +#### initial_superuser_name Name of the initial superuser role, passed to initdb when a new tenant is initialized. It doesn't affect anything after initialization. The default is Note: The default is 'zenith_admin', and the console depends on that, so if you change it, bad things will happen. -### page_cache_size +#### page_cache_size Size of the page cache, to hold materialized page versions. Unit is number of 8 kB blocks. The default is 8192, which means 64 MB. -### max_file_descriptors +#### max_file_descriptors Max number of file descriptors to hold open concurrently for accessing layer files. This should be kept well below the process/container/OS limit (see `ulimit -n`), as the pageserver also needs file descriptors for other files and for sockets for incoming connections. -### postgres-distrib +#### pg_distrib_dir A directory with Postgres installation to use during pageserver activities. Inside that dir, a `bin/postgres` binary should be present. The default distrib dir is `./tmp_install/`. -### workdir (-D) +#### workdir (-D) A directory in the file system, where pageserver will store its files. The default is `./.zenith/`. -### Remote storage +This parameter has a special CLI alias (`-D`) and can not be overridden with regular `-c` way. -There's a way to automatically backup and restore some of the pageserver's data from working dir to the remote storage. +##### Remote storage + +There's a way to automatically back up and restore some of the pageserver's data from working dir to the remote storage. The backup system is disabled by default and can be enabled for either of the currently available storages: -#### Local FS storage - -##### remote-storage-local-path +###### Local FS storage Pageserver can back up and restore some of its workdir contents to another directory. -For that, only a path to that directory needs to be specified as a parameter. +For that, only a path to that directory needs to be specified as a parameter: -#### S3 storage +```toml +[remote_storage] +local_path = '/some/local/path/' +``` + +###### S3 storage Pageserver can back up and restore some of its workdir contents to S3. -Full set of S3 credentials is needed for that as parameters: +Full set of S3 credentials is needed for that as parameters. +Configuration example: -##### remote-storage-s3-bucket +```toml +[remote_storage] +# Name of the bucket to connect to +bucket_name = 'some-sample-bucket' -Name of the bucket to connect to, example: "some-sample-bucket". +# Name of the region where the bucket is located at +bucket_region = 'eu-north-1' -##### remote-storage-region +# Access key to connect to the bucket ("login" part of the credentials) +access_key_id = 'SOMEKEYAAAAASADSAH*#' -Name of the region where the bucket is located at, example: "eu-north-1" +# Secret access key to connect to the bucket ("password" part of the credentials) +secret_access_key = 'SOMEsEcReTsd292v' +``` -##### remote-storage-access-key - -Access key to connect to the bucket ("login" part of the credentials), example: "AKIAIOSFODNN7EXAMPLE" - -##### remote-storage-secret-access-key - -Secret access key to connect to the bucket ("password" part of the credentials), example: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" - -#### General remote storage configuration +###### General remote storage configuration Pagesever allows only one remote storage configured concurrently and errors if parameters from multiple different remote configurations are used. No default values are used for the remote storage configuration parameters. -##### remote-storage-max-concurrent-sync +Besides, there are parameters common for all types of remote storage that can be configured, those have defaults: + +```toml +[remote_storage] +# Max number of concurrent connections to open for uploading to or downloading from the remote storage. +max_concurrent_sync = 100 + +# Max number of errors a single task can have before it's considered failed and not attempted to run anymore. +max_sync_errors = 10 +``` -Max number of concurrent connections to open for uploading to or -downloading from S3. -The default value is 100. ## safekeeper diff --git a/pageserver/Cargo.toml b/pageserver/Cargo.toml index 571d8938a2..ea3e630ccc 100644 --- a/pageserver/Cargo.toml +++ b/pageserver/Cargo.toml @@ -30,7 +30,7 @@ tar = "0.4.33" humantime = "2.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1" -toml = "0.5" +toml_edit = { version = "0.12", features = ["easy"] } scopeguard = "1.1.0" async-trait = "0.1" const_format = "0.2.21" diff --git a/pageserver/README.md b/pageserver/README.md index 7d1758b1dc..7d4b1f939c 100644 --- a/pageserver/README.md +++ b/pageserver/README.md @@ -134,8 +134,8 @@ Implementation details are covered in the [backup readme](./src/remote_storage/R The backup service is disabled by default and can be enabled to interact with a single remote storage. CLI examples: -* Local FS: `${PAGESERVER_BIN} --remote-storage-local-path="/some/local/path/"` -* AWS S3 : `${PAGESERVER_BIN} --remote-storage-s3-bucket="some-sample-bucket" --remote-storage-region="eu-north-1" --remote-storage-access-key="SOMEKEYAAAAASADSAH*#" --remote-storage-secret-access-key="SOMEsEcReTsd292v"` +* Local FS: `${PAGESERVER_BIN} -c "remote_storage={local_path='/some/local/path/'}"` +* AWS S3 : `${PAGESERVER_BIN} -c "remote_storage={bucket_name='some-sample-bucket',bucket_region='eu-north-1',access_key_id='SOMEKEYAAAAASADSAH*#',secret_access_key='SOMEsEcReTsd292v'}"` For Amazon AWS S3, a key id and secret access key could be located in `~/.aws/credentials` if awscli was ever configured to work with the desired bucket, on the AWS Settings page for a certain user. Also note, that the bucket names does not contain any protocols when used on AWS. For local S3 installations, refer to the their documentation for name format and credentials. diff --git a/pageserver/src/bin/pageserver.rs b/pageserver/src/bin/pageserver.rs index 0f8c2ea577..9e20ffa72e 100644 --- a/pageserver/src/bin/pageserver.rs +++ b/pageserver/src/bin/pageserver.rs @@ -1,26 +1,18 @@ -// -// Main entry point for the Page Server executable -// +//! Main entry point for the Page Server executable. -use serde::{Deserialize, Serialize}; -use std::{ - env, - num::{NonZeroU32, NonZeroUsize}, - path::{Path, PathBuf}, - str::FromStr, - thread, -}; +use std::{env, path::Path, str::FromStr, thread}; use tracing::*; use zenith_utils::{auth::JwtAuth, logging, postgres_backend::AuthType, tcp_listener, GIT_VERSION}; -use anyhow::{bail, ensure, Context, Result}; +use anyhow::{bail, Context, Result}; -use clap::{App, Arg, ArgMatches}; +use clap::{App, Arg}; use daemonize::Daemonize; use pageserver::{ - branches, defaults::*, http, page_cache, page_service, remote_storage, tenant_mgr, - virtual_file, PageServerConf, RemoteStorageConfig, RemoteStorageKind, S3Config, LOG_FILE_NAME, + branches, + config::{defaults::*, PageServerConf}, + http, page_cache, page_service, remote_storage, tenant_mgr, virtual_file, LOG_FILE_NAME, }; use zenith_utils::http::endpoint; use zenith_utils::postgres_backend; @@ -29,258 +21,6 @@ use zenith_utils::signals::{self, Signal}; use const_format::formatcp; -/// String arguments that can be declared via CLI or config file -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -struct CfgFileParams { - listen_pg_addr: Option, - listen_http_addr: Option, - checkpoint_distance: Option, - checkpoint_period: Option, - gc_horizon: Option, - gc_period: Option, - open_mem_limit: Option, - page_cache_size: Option, - max_file_descriptors: Option, - pg_distrib_dir: Option, - auth_validation_public_key_path: Option, - auth_type: Option, - remote_storage_max_concurrent_sync: Option, - remote_storage_max_sync_errors: Option, - ///////////////////////////////// - //// Don't put `Option` and other "simple" values below. - //// - /// `Option` is a table in TOML. - /// Values in TOML cannot be defined after tables (other tables can), - /// and [`toml`] crate serializes all fields in the order of their appearance. - //////////////////////////////// - remote_storage: Option, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -// Without this attribute, enums with values won't be serialized by the `toml` library (but can be deserialized nonetheless!). -// See https://github.com/alexcrichton/toml-rs/blob/6c162e6562c3e432bf04c82a3d1d789d80761a86/examples/enum_external.rs for the examples -#[serde(untagged)] -enum RemoteStorage { - Local { - local_path: String, - }, - AwsS3 { - bucket_name: String, - bucket_region: String, - #[serde(skip_serializing)] - access_key_id: Option, - #[serde(skip_serializing)] - secret_access_key: Option, - }, -} - -impl CfgFileParams { - /// Extract string arguments from CLI - fn from_args(arg_matches: &ArgMatches) -> Self { - let get_arg = |arg_name: &str| -> Option { - arg_matches.value_of(arg_name).map(str::to_owned) - }; - - let remote_storage = if let Some(local_path) = get_arg("remote-storage-local-path") { - Some(RemoteStorage::Local { local_path }) - } else if let Some((bucket_name, bucket_region)) = - get_arg("remote-storage-s3-bucket").zip(get_arg("remote-storage-region")) - { - Some(RemoteStorage::AwsS3 { - bucket_name, - bucket_region, - access_key_id: get_arg("remote-storage-access-key"), - secret_access_key: get_arg("remote-storage-secret-access-key"), - }) - } else { - None - }; - - Self { - listen_pg_addr: get_arg("listen_pg_addr"), - listen_http_addr: get_arg("listen_http_addr"), - checkpoint_distance: get_arg("checkpoint_distance"), - checkpoint_period: get_arg("checkpoint_period"), - gc_horizon: get_arg("gc_horizon"), - gc_period: get_arg("gc_period"), - open_mem_limit: get_arg("open_mem_limit"), - page_cache_size: get_arg("page_cache_size"), - max_file_descriptors: get_arg("max_file_descriptors"), - pg_distrib_dir: get_arg("postgres-distrib"), - auth_validation_public_key_path: get_arg("auth-validation-public-key-path"), - auth_type: get_arg("auth-type"), - remote_storage, - remote_storage_max_concurrent_sync: get_arg("remote-storage-max-concurrent-sync"), - remote_storage_max_sync_errors: get_arg("remote-storage-max-sync-errors"), - } - } - - /// Fill missing values in `self` with `other` - fn or(self, other: CfgFileParams) -> Self { - // TODO cleaner way to do this - Self { - listen_pg_addr: self.listen_pg_addr.or(other.listen_pg_addr), - listen_http_addr: self.listen_http_addr.or(other.listen_http_addr), - checkpoint_distance: self.checkpoint_distance.or(other.checkpoint_distance), - checkpoint_period: self.checkpoint_period.or(other.checkpoint_period), - gc_horizon: self.gc_horizon.or(other.gc_horizon), - gc_period: self.gc_period.or(other.gc_period), - open_mem_limit: self.open_mem_limit.or(other.open_mem_limit), - page_cache_size: self.page_cache_size.or(other.page_cache_size), - max_file_descriptors: self.max_file_descriptors.or(other.max_file_descriptors), - pg_distrib_dir: self.pg_distrib_dir.or(other.pg_distrib_dir), - auth_validation_public_key_path: self - .auth_validation_public_key_path - .or(other.auth_validation_public_key_path), - auth_type: self.auth_type.or(other.auth_type), - remote_storage: self.remote_storage.or(other.remote_storage), - remote_storage_max_concurrent_sync: self - .remote_storage_max_concurrent_sync - .or(other.remote_storage_max_concurrent_sync), - remote_storage_max_sync_errors: self - .remote_storage_max_sync_errors - .or(other.remote_storage_max_sync_errors), - } - } - - /// Create a PageServerConf from these string parameters - fn try_into_config(&self) -> Result { - let workdir = PathBuf::from("."); - - let listen_pg_addr = match self.listen_pg_addr.as_ref() { - Some(addr) => addr.clone(), - None => DEFAULT_PG_LISTEN_ADDR.to_owned(), - }; - - let listen_http_addr = match self.listen_http_addr.as_ref() { - Some(addr) => addr.clone(), - None => DEFAULT_HTTP_LISTEN_ADDR.to_owned(), - }; - - let checkpoint_distance: u64 = match self.checkpoint_distance.as_ref() { - Some(checkpoint_distance_str) => checkpoint_distance_str.parse()?, - None => DEFAULT_CHECKPOINT_DISTANCE, - }; - let checkpoint_period = match self.checkpoint_period.as_ref() { - Some(checkpoint_period_str) => humantime::parse_duration(checkpoint_period_str)?, - None => DEFAULT_CHECKPOINT_PERIOD, - }; - - let gc_horizon: u64 = match self.gc_horizon.as_ref() { - Some(horizon_str) => horizon_str.parse()?, - None => DEFAULT_GC_HORIZON, - }; - let gc_period = match self.gc_period.as_ref() { - Some(period_str) => humantime::parse_duration(period_str)?, - None => DEFAULT_GC_PERIOD, - }; - - let open_mem_limit: usize = match self.open_mem_limit.as_ref() { - Some(open_mem_limit_str) => open_mem_limit_str.parse()?, - None => DEFAULT_OPEN_MEM_LIMIT, - }; - - let page_cache_size: usize = match self.page_cache_size.as_ref() { - Some(page_cache_size_str) => page_cache_size_str.parse()?, - None => DEFAULT_PAGE_CACHE_SIZE, - }; - - let max_file_descriptors: usize = match self.max_file_descriptors.as_ref() { - Some(max_file_descriptors_str) => max_file_descriptors_str.parse()?, - None => DEFAULT_MAX_FILE_DESCRIPTORS, - }; - - let pg_distrib_dir = match self.pg_distrib_dir.as_ref() { - Some(pg_distrib_dir_str) => PathBuf::from(pg_distrib_dir_str), - None => env::current_dir()?.join("tmp_install"), - }; - - let auth_validation_public_key_path = self - .auth_validation_public_key_path - .as_ref() - .map(PathBuf::from); - - let auth_type = self - .auth_type - .as_ref() - .map_or(Ok(AuthType::Trust), |auth_type| { - AuthType::from_str(auth_type) - })?; - - if !pg_distrib_dir.join("bin/postgres").exists() { - bail!("Can't find postgres binary at {:?}", pg_distrib_dir); - } - - if auth_type == AuthType::ZenithJWT { - ensure!( - auth_validation_public_key_path.is_some(), - "Missing auth_validation_public_key_path when auth_type is ZenithJWT" - ); - let path_ref = auth_validation_public_key_path.as_ref().unwrap(); - ensure!( - path_ref.exists(), - format!("Can't find auth_validation_public_key at {:?}", path_ref) - ); - } - - let max_concurrent_sync = match self.remote_storage_max_concurrent_sync.as_deref() { - Some(number_str) => number_str.parse()?, - None => NonZeroUsize::new(DEFAULT_REMOTE_STORAGE_MAX_CONCURRENT_SYNC).unwrap(), - }; - let max_sync_errors = match self.remote_storage_max_sync_errors.as_deref() { - Some(number_str) => number_str.parse()?, - None => NonZeroU32::new(DEFAULT_REMOTE_STORAGE_MAX_SYNC_ERRORS).unwrap(), - }; - let remote_storage_config = self.remote_storage.as_ref().map(|storage_params| { - let storage = match storage_params.clone() { - RemoteStorage::Local { local_path } => { - RemoteStorageKind::LocalFs(PathBuf::from(local_path)) - } - RemoteStorage::AwsS3 { - bucket_name, - bucket_region, - access_key_id, - secret_access_key, - } => RemoteStorageKind::AwsS3(S3Config { - bucket_name, - bucket_region, - access_key_id, - secret_access_key, - }), - }; - RemoteStorageConfig { - max_concurrent_sync, - max_sync_errors, - storage, - } - }); - - Ok(PageServerConf { - daemonize: false, - - listen_pg_addr, - listen_http_addr, - checkpoint_distance, - checkpoint_period, - gc_horizon, - gc_period, - open_mem_limit, - page_cache_size, - max_file_descriptors, - - superuser: String::from(DEFAULT_SUPERUSER), - - workdir, - - pg_distrib_dir, - - auth_validation_public_key_path, - auth_type, - remote_storage_config, - }) - } -} - fn main() -> Result<()> { zenith_metrics::set_common_metrics_prefix("pageserver"); let arg_matches = App::new("Zenith page server") @@ -314,49 +54,6 @@ fn main() -> Result<()> { .takes_value(false) .help("Initialize pageserver repo"), ) - .arg( - Arg::with_name("checkpoint_distance") - .long("checkpoint_distance") - .takes_value(true) - .help("Distance from current LSN to perform checkpoint of in-memory layers"), - ) - .arg( - Arg::with_name("checkpoint_period") - .long("checkpoint_period") - .takes_value(true) - .help("Interval between checkpoint iterations"), - ) - .arg( - Arg::with_name("gc_horizon") - .long("gc_horizon") - .takes_value(true) - .help("Distance from current LSN to perform all wal records cleanup"), - ) - .arg( - Arg::with_name("gc_period") - .long("gc_period") - .takes_value(true) - .help("Interval between garbage collector iterations"), - ) - .arg( - Arg::with_name("open_mem_limit") - .long("open_mem_limit") - .takes_value(true) - .help("Amount of memory reserved for buffering incoming WAL"), - ) - .arg( - - Arg::with_name("page_cache_size") - .long("page_cache_size") - .takes_value(true) - .help("Number of pages in the page cache"), - ) - .arg( - Arg::with_name("max_file_descriptors") - .long("max_file_descriptors") - .takes_value(true) - .help("Max number of file descriptors to keep open for files"), - ) .arg( Arg::with_name("workdir") .short("D") @@ -364,12 +61,6 @@ fn main() -> Result<()> { .takes_value(true) .help("Working directory for the pageserver"), ) - .arg( - Arg::with_name("postgres-distrib") - .long("postgres-distrib") - .takes_value(true) - .help("Postgres distribution directory"), - ) .arg( Arg::with_name("create-tenant") .long("create-tenant") @@ -377,60 +68,15 @@ fn main() -> Result<()> { .help("Create tenant during init") .requires("init"), ) + // See `settings.md` for more details on the extra configuration patameters pageserver can process .arg( - Arg::with_name("auth-validation-public-key-path") - .long("auth-validation-public-key-path") + Arg::with_name("config-option") + .short("c") .takes_value(true) - .help("Path to public key used to validate jwt signature"), - ) - .arg( - Arg::with_name("auth-type") - .long("auth-type") - .takes_value(true) - .help("Authentication scheme type. One of: Trust, MD5, ZenithJWT"), - ) - .arg( - Arg::with_name("remote-storage-local-path") - .long("remote-storage-local-path") - .takes_value(true) - .help("Path to the local directory, to be used as an external remote storage") - .conflicts_with_all(&[ - "remote-storage-s3-bucket", - "remote-storage-region", - "remote-storage-access-key", - "remote-storage-secret-access-key", - ]), - ) - .arg( - Arg::with_name("remote-storage-s3-bucket") - .long("remote-storage-s3-bucket") - .takes_value(true) - .help("Name of the AWS S3 bucket to use an external remote storage") - .requires("remote-storage-region"), - ) - .arg( - Arg::with_name("remote-storage-region") - .long("remote-storage-region") - .takes_value(true) - .help("Region of the AWS S3 bucket"), - ) - .arg( - Arg::with_name("remote-storage-access-key") - .long("remote-storage-access-key") - .takes_value(true) - .help("Credentials to access the AWS S3 bucket"), - ) - .arg( - Arg::with_name("remote-storage-secret-access-key") - .long("remote-storage-secret-access-key") - .takes_value(true) - .help("Credentials to access the AWS S3 bucket"), - ) - .arg( - Arg::with_name("remote-storage-max-concurrent-sync") - .long("remote-storage-max-concurrent-sync") - .takes_value(true) - .help("Maximum allowed concurrent synchronisations with storage"), + .number_of_values(1) + .multiple(true) + .help("Additional configuration options or overrides of the ones from the toml config file. + Any option has to be a valid toml document, example: `-c \"foo='hey'\"` `-c \"foo={value=1}\"`"), ) .get_matches(); @@ -440,27 +86,9 @@ fn main() -> Result<()> { .with_context(|| format!("Error opening workdir '{}'", workdir.display()))? .join("pageserver.toml"); - let args_params = CfgFileParams::from_args(&arg_matches); - let init = arg_matches.is_present("init"); let create_tenant = arg_matches.value_of("create-tenant"); - let params = if init { - // We're initializing the repo, so there's no config file yet - args_params - } else { - // Supplement the CLI arguments with the config file - let cfg_file_contents = std::fs::read_to_string(&cfg_file_path) - .with_context(|| format!("No pageserver config at '{}'", cfg_file_path.display()))?; - let file_params: CfgFileParams = toml::from_str(&cfg_file_contents).with_context(|| { - format!( - "Failed to read '{}' as pageserver config", - cfg_file_path.display() - ) - })?; - args_params.or(file_params) - }; - // Set CWD to workdir for non-daemon modes env::set_current_dir(&workdir).with_context(|| { format!( @@ -469,20 +97,48 @@ fn main() -> Result<()> { ) })?; - // Ensure the config is valid, even if just init-ing - let mut conf = params.try_into_config().with_context(|| { - format!( - "Pageserver config at '{}' is not valid", - cfg_file_path.display() - ) - })?; - - conf.daemonize = arg_matches.is_present("daemonize"); - - if init && conf.daemonize { + let daemonize = arg_matches.is_present("daemonize"); + if init && daemonize { bail!("--daemonize cannot be used with --init") } + let mut toml = if init { + // We're initializing the repo, so there's no config file yet + DEFAULT_CONFIG_FILE + .parse::() + .expect("could not parse built-in config file") + } else { + // Supplement the CLI arguments with the config file + let cfg_file_contents = std::fs::read_to_string(&cfg_file_path) + .with_context(|| format!("No pageserver config at '{}'", cfg_file_path.display()))?; + cfg_file_contents + .parse::() + .with_context(|| { + format!( + "Failed to read '{}' as pageserver config", + cfg_file_path.display() + ) + })? + }; + + // Process any extra options given with -c + if let Some(values) = arg_matches.values_of("config-option") { + for option_line in values { + let doc = toml_edit::Document::from_str(option_line).with_context(|| { + format!( + "Option '{}' could not be parsed as a toml document", + option_line + ) + })?; + for (key, item) in doc.iter() { + toml.insert(key, item.clone()); + } + } + } + trace!("Resulting toml: {}", toml); + let conf = PageServerConf::parse_and_validate(&toml, workdir) + .context("Failed to parse pageserver configuration")?; + // The configuration is all set up now. Turn it into a 'static // that can be freely stored in structs and passed across threads // as a ref. @@ -497,10 +153,7 @@ fn main() -> Result<()> { if init { branches::init_pageserver(conf, create_tenant).context("Failed to init pageserver")?; // write the config file - let cfg_file_contents = toml::to_string_pretty(¶ms) - .context("Failed to create pageserver config contents for initialisation")?; - // TODO support enable-auth flag - std::fs::write(&cfg_file_path, cfg_file_contents).with_context(|| { + std::fs::write(&cfg_file_path, toml.to_string()).with_context(|| { format!( "Failed to initialize pageserver config at '{}'", cfg_file_path.display() @@ -508,13 +161,13 @@ fn main() -> Result<()> { })?; Ok(()) } else { - start_pageserver(conf).context("Failed to start pageserver") + start_pageserver(conf, daemonize).context("Failed to start pageserver") } } -fn start_pageserver(conf: &'static PageServerConf) -> Result<()> { +fn start_pageserver(conf: &'static PageServerConf, daemonize: bool) -> Result<()> { // Initialize logger - let log_file = logging::init(LOG_FILE_NAME, conf.daemonize)?; + let log_file = logging::init(LOG_FILE_NAME, daemonize)?; info!("version: {}", GIT_VERSION); @@ -534,7 +187,7 @@ fn start_pageserver(conf: &'static PageServerConf) -> Result<()> { let pageserver_listener = tcp_listener::bind(conf.listen_pg_addr.clone())?; // XXX: Don't spawn any threads before daemonizing! - if conf.daemonize { + if daemonize { info!("daemonizing..."); // There shouldn't be any logging to stdin/stdout. Redirect it to the main log so @@ -635,154 +288,3 @@ fn start_pageserver(conf: &'static PageServerConf) -> Result<()> { } }) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn page_server_conf_toml_serde() { - let params = CfgFileParams { - listen_pg_addr: Some("listen_pg_addr_VALUE".to_string()), - listen_http_addr: Some("listen_http_addr_VALUE".to_string()), - checkpoint_distance: Some("checkpoint_distance_VALUE".to_string()), - checkpoint_period: Some("checkpoint_period_VALUE".to_string()), - gc_horizon: Some("gc_horizon_VALUE".to_string()), - gc_period: Some("gc_period_VALUE".to_string()), - open_mem_limit: Some("open_mem_limit_VALUE".to_string()), - page_cache_size: Some("page_cache_size_VALUE".to_string()), - max_file_descriptors: Some("max_file_descriptors_VALUE".to_string()), - pg_distrib_dir: Some("pg_distrib_dir_VALUE".to_string()), - auth_validation_public_key_path: Some( - "auth_validation_public_key_path_VALUE".to_string(), - ), - auth_type: Some("auth_type_VALUE".to_string()), - remote_storage: Some(RemoteStorage::Local { - local_path: "remote_storage_local_VALUE".to_string(), - }), - remote_storage_max_concurrent_sync: Some( - "remote_storage_max_concurrent_sync_VALUE".to_string(), - ), - remote_storage_max_sync_errors: Some( - "remote_storage_max_sync_errors_VALUE".to_string(), - ), - }; - - let toml_string = toml::to_string(¶ms).expect("Failed to serialize correct config"); - let toml_pretty_string = - toml::to_string_pretty(¶ms).expect("Failed to serialize correct config"); - assert_eq!( - r#"listen_pg_addr = 'listen_pg_addr_VALUE' -listen_http_addr = 'listen_http_addr_VALUE' -checkpoint_distance = 'checkpoint_distance_VALUE' -checkpoint_period = 'checkpoint_period_VALUE' -gc_horizon = 'gc_horizon_VALUE' -gc_period = 'gc_period_VALUE' -open_mem_limit = 'open_mem_limit_VALUE' -page_cache_size = 'page_cache_size_VALUE' -max_file_descriptors = 'max_file_descriptors_VALUE' -pg_distrib_dir = 'pg_distrib_dir_VALUE' -auth_validation_public_key_path = 'auth_validation_public_key_path_VALUE' -auth_type = 'auth_type_VALUE' -remote_storage_max_concurrent_sync = 'remote_storage_max_concurrent_sync_VALUE' -remote_storage_max_sync_errors = 'remote_storage_max_sync_errors_VALUE' - -[remote_storage] -local_path = 'remote_storage_local_VALUE' -"#, - toml_pretty_string - ); - - let params_from_serialized: CfgFileParams = toml::from_str(&toml_string) - .expect("Failed to deserialize the serialization result of the config"); - let params_from_serialized_pretty: CfgFileParams = toml::from_str(&toml_pretty_string) - .expect("Failed to deserialize the prettified serialization result of the config"); - assert!( - params_from_serialized == params, - "Expected the same config in the end of config -> serialize -> deserialize chain" - ); - assert!( - params_from_serialized_pretty == params, - "Expected the same config in the end of config -> serialize pretty -> deserialize chain" - ); - } - - #[test] - fn credentials_omitted_during_serialization() { - let params = CfgFileParams { - listen_pg_addr: Some("listen_pg_addr_VALUE".to_string()), - listen_http_addr: Some("listen_http_addr_VALUE".to_string()), - checkpoint_distance: Some("checkpoint_distance_VALUE".to_string()), - checkpoint_period: Some("checkpoint_period_VALUE".to_string()), - gc_horizon: Some("gc_horizon_VALUE".to_string()), - gc_period: Some("gc_period_VALUE".to_string()), - open_mem_limit: Some("open_mem_limit_VALUE".to_string()), - page_cache_size: Some("page_cache_size_VALUE".to_string()), - max_file_descriptors: Some("max_file_descriptors_VALUE".to_string()), - pg_distrib_dir: Some("pg_distrib_dir_VALUE".to_string()), - auth_validation_public_key_path: Some( - "auth_validation_public_key_path_VALUE".to_string(), - ), - auth_type: Some("auth_type_VALUE".to_string()), - remote_storage: Some(RemoteStorage::AwsS3 { - bucket_name: "bucket_name_VALUE".to_string(), - bucket_region: "bucket_region_VALUE".to_string(), - access_key_id: Some("access_key_id_VALUE".to_string()), - secret_access_key: Some("secret_access_key_VALUE".to_string()), - }), - remote_storage_max_concurrent_sync: Some( - "remote_storage_max_concurrent_sync_VALUE".to_string(), - ), - remote_storage_max_sync_errors: Some( - "remote_storage_max_sync_errors_VALUE".to_string(), - ), - }; - - let toml_string = toml::to_string(¶ms).expect("Failed to serialize correct config"); - let toml_pretty_string = - toml::to_string_pretty(¶ms).expect("Failed to serialize correct config"); - assert_eq!( - r#"listen_pg_addr = 'listen_pg_addr_VALUE' -listen_http_addr = 'listen_http_addr_VALUE' -checkpoint_distance = 'checkpoint_distance_VALUE' -checkpoint_period = 'checkpoint_period_VALUE' -gc_horizon = 'gc_horizon_VALUE' -gc_period = 'gc_period_VALUE' -open_mem_limit = 'open_mem_limit_VALUE' -page_cache_size = 'page_cache_size_VALUE' -max_file_descriptors = 'max_file_descriptors_VALUE' -pg_distrib_dir = 'pg_distrib_dir_VALUE' -auth_validation_public_key_path = 'auth_validation_public_key_path_VALUE' -auth_type = 'auth_type_VALUE' -remote_storage_max_concurrent_sync = 'remote_storage_max_concurrent_sync_VALUE' -remote_storage_max_sync_errors = 'remote_storage_max_sync_errors_VALUE' - -[remote_storage] -bucket_name = 'bucket_name_VALUE' -bucket_region = 'bucket_region_VALUE' -"#, - toml_pretty_string - ); - - let params_from_serialized: CfgFileParams = toml::from_str(&toml_string) - .expect("Failed to deserialize the serialization result of the config"); - let params_from_serialized_pretty: CfgFileParams = toml::from_str(&toml_pretty_string) - .expect("Failed to deserialize the prettified serialization result of the config"); - - let mut expected_params = params; - expected_params.remote_storage = Some(RemoteStorage::AwsS3 { - bucket_name: "bucket_name_VALUE".to_string(), - bucket_region: "bucket_region_VALUE".to_string(), - access_key_id: None, - secret_access_key: None, - }); - assert!( - params_from_serialized == expected_params, - "Expected the config without credentials in the end of a 'config -> serialize -> deserialize' chain" - ); - assert!( - params_from_serialized_pretty == expected_params, - "Expected the config without credentials in the end of a 'config -> serialize pretty -> deserialize' chain" - ); - } -} diff --git a/pageserver/src/branches.rs b/pageserver/src/branches.rs index 62c18d1f20..f757431bed 100644 --- a/pageserver/src/branches.rs +++ b/pageserver/src/branches.rs @@ -23,8 +23,8 @@ use zenith_utils::zid::{ZTenantId, ZTimelineId}; use crate::walredo::WalRedoManager; use crate::CheckpointConfig; +use crate::{config::PageServerConf, repository::Repository}; use crate::{import_datadir, LOG_FILE_NAME}; -use crate::{repository::Repository, PageServerConf}; use crate::{repository::RepositoryTimeline, tenant_mgr}; #[derive(Serialize, Deserialize, Clone)] diff --git a/pageserver/src/config.rs b/pageserver/src/config.rs new file mode 100644 index 0000000000..de4e1f2fa9 --- /dev/null +++ b/pageserver/src/config.rs @@ -0,0 +1,662 @@ +//! Functions for handling page server configuration options +//! +//! Configuration options can be set in the pageserver.toml configuration +//! file, or on the command line. +//! See also `settings.md` for better description on every parameter. + +use anyhow::{anyhow, bail, ensure, Context, Result}; +use toml_edit; +use toml_edit::{Document, Item}; +use zenith_utils::postgres_backend::AuthType; +use zenith_utils::zid::{ZTenantId, ZTimelineId}; + +use std::convert::TryInto; +use std::env; +use std::num::{NonZeroU32, NonZeroUsize}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::time::Duration; + +use crate::layered_repository::TIMELINES_SEGMENT_NAME; + +pub mod defaults { + use const_format::formatcp; + + pub const DEFAULT_PG_LISTEN_PORT: u16 = 64000; + pub const DEFAULT_PG_LISTEN_ADDR: &str = formatcp!("127.0.0.1:{DEFAULT_PG_LISTEN_PORT}"); + pub const DEFAULT_HTTP_LISTEN_PORT: u16 = 9898; + pub const DEFAULT_HTTP_LISTEN_ADDR: &str = formatcp!("127.0.0.1:{DEFAULT_HTTP_LISTEN_PORT}"); + + // FIXME: This current value is very low. I would imagine something like 1 GB or 10 GB + // would be more appropriate. But a low value forces the code to be exercised more, + // which is good for now to trigger bugs. + pub const DEFAULT_CHECKPOINT_DISTANCE: u64 = 256 * 1024 * 1024; + pub const DEFAULT_CHECKPOINT_PERIOD: &str = "1 s"; + + pub const DEFAULT_GC_HORIZON: u64 = 64 * 1024 * 1024; + pub const DEFAULT_GC_PERIOD: &str = "100 s"; + + pub const DEFAULT_SUPERUSER: &str = "zenith_admin"; + pub const DEFAULT_REMOTE_STORAGE_MAX_CONCURRENT_SYNC: usize = 100; + pub const DEFAULT_REMOTE_STORAGE_MAX_SYNC_ERRORS: u32 = 10; + + pub const DEFAULT_PAGE_CACHE_SIZE: usize = 8192; + pub const DEFAULT_MAX_FILE_DESCRIPTORS: usize = 100; + + /// + /// Default built-in configuration file. + /// + pub const DEFAULT_CONFIG_FILE: &str = formatcp!( + r###" +# Initial configuration file created by 'pageserver --init' + +#listen_pg_addr = '{DEFAULT_PG_LISTEN_ADDR}' +#listen_http_addr = '{DEFAULT_HTTP_LISTEN_ADDR}' + +#checkpoint_distance = {DEFAULT_CHECKPOINT_DISTANCE} # in bytes +#checkpoint_period = '{DEFAULT_CHECKPOINT_PERIOD}' + +#gc_period = '{DEFAULT_GC_PERIOD}' +#gc_horizon = {DEFAULT_GC_HORIZON} + +#max_file_descriptors = {DEFAULT_MAX_FILE_DESCRIPTORS} + +# initial superuser role name to use when creating a new tenant +#initial_superuser_name = '{DEFAULT_SUPERUSER}' + +# [remote_storage] + +"### + ); +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PageServerConf { + /// Example (default): 127.0.0.1:64000 + pub listen_pg_addr: String, + /// Example (default): 127.0.0.1:9898 + pub listen_http_addr: String, + + // Flush out an inmemory layer, if it's holding WAL older than this + // This puts a backstop on how much WAL needs to be re-digested if the + // page server crashes. + pub checkpoint_distance: u64, + pub checkpoint_period: Duration, + + pub gc_horizon: u64, + pub gc_period: Duration, + pub superuser: String, + + pub page_cache_size: usize, + pub max_file_descriptors: usize, + + // Repository directory, relative to current working directory. + // Normally, the page server changes the current working directory + // to the repository, and 'workdir' is always '.'. But we don't do + // that during unit testing, because the current directory is global + // to the process but different unit tests work on different + // repositories. + pub workdir: PathBuf, + + pub pg_distrib_dir: PathBuf, + + pub auth_type: AuthType, + + pub auth_validation_public_key_path: Option, + pub remote_storage_config: Option, +} + +/// External backup storage configuration, enough for creating a client for that storage. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RemoteStorageConfig { + /// Max allowed number of concurrent sync operations between pageserver and the remote storage. + pub max_concurrent_sync: NonZeroUsize, + /// Max allowed errors before the sync task is considered failed and evicted. + pub max_sync_errors: NonZeroU32, + /// The storage connection configuration. + pub storage: RemoteStorageKind, +} + +/// A kind of a remote storage to connect to, with its connection configuration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RemoteStorageKind { + /// Storage based on local file system. + /// Specify a root folder to place all stored relish data into. + LocalFs(PathBuf), + /// AWS S3 based storage, storing all relishes into the root + /// of the S3 bucket from the config. + AwsS3(S3Config), +} + +/// AWS S3 bucket coordinates and access credentials to manage the bucket contents (read and write). +#[derive(Clone, PartialEq, Eq)] +pub struct S3Config { + /// Name of the bucket to connect to. + pub bucket_name: String, + /// The region where the bucket is located at. + pub bucket_region: String, + /// "Login" to use when connecting to bucket. + /// Can be empty for cases like AWS k8s IAM + /// where we can allow certain pods to connect + /// to the bucket directly without any credentials. + pub access_key_id: Option, + /// "Password" to use when connecting to bucket. + pub secret_access_key: Option, +} + +impl std::fmt::Debug for S3Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("S3Config") + .field("bucket_name", &self.bucket_name) + .field("bucket_region", &self.bucket_region) + .finish() + } +} + +impl PageServerConf { + // + // Repository paths, relative to workdir. + // + + pub fn tenants_path(&self) -> PathBuf { + self.workdir.join("tenants") + } + + pub fn tenant_path(&self, tenantid: &ZTenantId) -> PathBuf { + self.tenants_path().join(tenantid.to_string()) + } + + pub fn tags_path(&self, tenantid: &ZTenantId) -> PathBuf { + self.tenant_path(tenantid).join("refs").join("tags") + } + + pub fn tag_path(&self, tag_name: &str, tenantid: &ZTenantId) -> PathBuf { + self.tags_path(tenantid).join(tag_name) + } + + pub fn branches_path(&self, tenantid: &ZTenantId) -> PathBuf { + self.tenant_path(tenantid).join("refs").join("branches") + } + + pub fn branch_path(&self, branch_name: &str, tenantid: &ZTenantId) -> PathBuf { + self.branches_path(tenantid).join(branch_name) + } + + pub fn timelines_path(&self, tenantid: &ZTenantId) -> PathBuf { + self.tenant_path(tenantid).join(TIMELINES_SEGMENT_NAME) + } + + pub fn timeline_path(&self, timelineid: &ZTimelineId, tenantid: &ZTenantId) -> PathBuf { + self.timelines_path(tenantid).join(timelineid.to_string()) + } + + pub fn ancestor_path(&self, timelineid: &ZTimelineId, tenantid: &ZTenantId) -> PathBuf { + self.timeline_path(timelineid, tenantid).join("ancestor") + } + + // + // Postgres distribution paths + // + + pub fn pg_bin_dir(&self) -> PathBuf { + self.pg_distrib_dir.join("bin") + } + + pub fn pg_lib_dir(&self) -> PathBuf { + self.pg_distrib_dir.join("lib") + } + + /// Parse a configuration file (pageserver.toml) into a PageServerConf struct, + /// validating the input and failing on errors. + /// + /// This leaves any options not present in the file in the built-in defaults. + pub fn parse_and_validate(toml: &Document, workdir: &Path) -> Result { + use defaults::*; + + let mut conf = PageServerConf { + workdir: workdir.to_path_buf(), + + listen_pg_addr: DEFAULT_PG_LISTEN_ADDR.to_string(), + listen_http_addr: DEFAULT_HTTP_LISTEN_ADDR.to_string(), + checkpoint_distance: DEFAULT_CHECKPOINT_DISTANCE, + checkpoint_period: humantime::parse_duration(DEFAULT_CHECKPOINT_PERIOD)?, + gc_horizon: DEFAULT_GC_HORIZON, + gc_period: humantime::parse_duration(DEFAULT_GC_PERIOD)?, + page_cache_size: DEFAULT_PAGE_CACHE_SIZE, + max_file_descriptors: DEFAULT_MAX_FILE_DESCRIPTORS, + + pg_distrib_dir: PathBuf::new(), + auth_validation_public_key_path: None, + auth_type: AuthType::Trust, + + remote_storage_config: None, + + superuser: DEFAULT_SUPERUSER.to_string(), + }; + + for (key, item) in toml.iter() { + match key { + "listen_pg_addr" => conf.listen_pg_addr = parse_toml_string(key, item)?, + "listen_http_addr" => conf.listen_http_addr = parse_toml_string(key, item)?, + "checkpoint_distance" => conf.checkpoint_distance = parse_toml_u64(key, item)?, + "checkpoint_period" => conf.checkpoint_period = parse_toml_duration(key, item)?, + "gc_horizon" => conf.gc_horizon = parse_toml_u64(key, item)?, + "gc_period" => conf.gc_period = parse_toml_duration(key, item)?, + "initial_superuser_name" => conf.superuser = parse_toml_string(key, item)?, + "page_cache_size" => conf.page_cache_size = parse_toml_u64(key, item)? as usize, + "max_file_descriptors" => { + conf.max_file_descriptors = parse_toml_u64(key, item)? as usize + } + "pg_distrib_dir" => { + conf.pg_distrib_dir = PathBuf::from(parse_toml_string(key, item)?) + } + "auth_validation_public_key_path" => { + conf.auth_validation_public_key_path = + Some(PathBuf::from(parse_toml_string(key, item)?)) + } + "auth_type" => conf.auth_type = parse_toml_auth_type(key, item)?, + "remote_storage" => { + conf.remote_storage_config = Some(Self::parse_remote_storage_config(item)?) + } + _ => bail!("unrecognized pageserver option '{}'", key), + } + } + + if conf.auth_type == AuthType::ZenithJWT { + let auth_validation_public_key_path = conf + .auth_validation_public_key_path + .get_or_insert_with(|| workdir.join("auth_public_key.pem")); + ensure!( + auth_validation_public_key_path.exists(), + format!( + "Can't find auth_validation_public_key at '{}'", + auth_validation_public_key_path.display() + ) + ); + } + + if conf.pg_distrib_dir == PathBuf::new() { + conf.pg_distrib_dir = env::current_dir()?.join("tmp_install") + }; + if !conf.pg_distrib_dir.join("bin/postgres").exists() { + bail!( + "Can't find postgres binary at {}", + conf.pg_distrib_dir.display() + ); + } + + Ok(conf) + } + + /// subroutine of parse_config(), to parse the `[remote_storage]` table. + fn parse_remote_storage_config(toml: &toml_edit::Item) -> anyhow::Result { + let local_path = toml.get("local_path"); + let bucket_name = toml.get("bucket_name"); + let bucket_region = toml.get("bucket_region"); + + let max_concurrent_sync: NonZeroUsize = if let Some(s) = toml.get("max_concurrent_sync") { + parse_toml_u64("max_concurrent_sync", s) + .and_then(|toml_u64| { + toml_u64.try_into().with_context(|| { + format!("'max_concurrent_sync' value {} is too large", toml_u64) + }) + }) + .ok() + .and_then(NonZeroUsize::new) + .ok_or_else(|| { + anyhow!("'max_concurrent_sync' must be a non-zero positive integer") + })? + } else { + NonZeroUsize::new(defaults::DEFAULT_REMOTE_STORAGE_MAX_CONCURRENT_SYNC).unwrap() + }; + let max_sync_errors: NonZeroU32 = if let Some(s) = toml.get("max_sync_errors") { + parse_toml_u64("max_sync_errors", s) + .and_then(|toml_u64| { + toml_u64.try_into().with_context(|| { + format!("'max_sync_errors' value {} is too large", toml_u64) + }) + }) + .ok() + .and_then(NonZeroU32::new) + .ok_or_else(|| anyhow!("'max_sync_errors' must be a non-zero positive integer"))? + } else { + NonZeroU32::new(defaults::DEFAULT_REMOTE_STORAGE_MAX_SYNC_ERRORS).unwrap() + }; + + let storage = match (local_path, bucket_name, bucket_region) { + (None, None, None) => bail!("no 'local_path' nor 'bucket_name' option"), + (_, Some(_), None) => { + bail!("'bucket_region' option is mandatory if 'bucket_name' is given ") + } + (_, None, Some(_)) => { + bail!("'bucket_name' option is mandatory if 'bucket_region' is given ") + } + (None, Some(bucket_name), Some(bucket_region)) => RemoteStorageKind::AwsS3(S3Config { + bucket_name: bucket_name.as_str().unwrap().to_string(), + bucket_region: bucket_region.as_str().unwrap().to_string(), + access_key_id: toml + .get("access_key_id") + .map(|x| x.as_str().unwrap().to_string()), + secret_access_key: toml + .get("secret_access_key") + .map(|x| x.as_str().unwrap().to_string()), + }), + (Some(local_path), None, None) => { + RemoteStorageKind::LocalFs(PathBuf::from(local_path.as_str().unwrap())) + } + (Some(_), Some(_), _) => bail!("local_path and bucket_name are mutually exclusive"), + }; + + Ok(RemoteStorageConfig { + max_concurrent_sync, + max_sync_errors, + storage, + }) + } + + #[cfg(test)] + pub fn test_repo_dir(test_name: &str) -> PathBuf { + PathBuf::from(format!("../tmp_check/test_{}", test_name)) + } + + #[cfg(test)] + pub fn dummy_conf(repo_dir: PathBuf) -> Self { + PageServerConf { + checkpoint_distance: defaults::DEFAULT_CHECKPOINT_DISTANCE, + checkpoint_period: Duration::from_secs(10), + gc_horizon: defaults::DEFAULT_GC_HORIZON, + gc_period: Duration::from_secs(10), + page_cache_size: defaults::DEFAULT_PAGE_CACHE_SIZE, + max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS, + listen_pg_addr: defaults::DEFAULT_PG_LISTEN_ADDR.to_string(), + listen_http_addr: defaults::DEFAULT_HTTP_LISTEN_ADDR.to_string(), + superuser: "zenith_admin".to_string(), + workdir: repo_dir, + pg_distrib_dir: PathBuf::new(), + auth_type: AuthType::Trust, + auth_validation_public_key_path: None, + remote_storage_config: None, + } + } +} + +// Helper functions to parse a toml Item + +fn parse_toml_string(name: &str, item: &Item) -> Result { + let s = item + .as_str() + .ok_or_else(|| anyhow!("configure option {} is not a string", name))?; + Ok(s.to_string()) +} + +fn parse_toml_u64(name: &str, item: &Item) -> Result { + // A toml integer is signed, so it cannot represent the full range of an u64. That's OK + // for our use, though. + let i: i64 = item + .as_integer() + .ok_or_else(|| anyhow!("configure option {} is not an integer", name))?; + if i < 0 { + bail!("configure option {} cannot be negative", name); + } + Ok(i as u64) +} + +fn parse_toml_duration(name: &str, item: &Item) -> Result { + let s = item + .as_str() + .ok_or_else(|| anyhow!("configure option {} is not a string", name))?; + + Ok(humantime::parse_duration(s)?) +} + +fn parse_toml_auth_type(name: &str, item: &Item) -> Result { + let v = item + .as_str() + .ok_or_else(|| anyhow!("configure option {} is not a string", name))?; + AuthType::from_str(v) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::{tempdir, TempDir}; + + use super::*; + + const ALL_BASE_VALUES_TOML: &str = r#" +# Initial configuration file created by 'pageserver --init' + +listen_pg_addr = '127.0.0.1:64000' +listen_http_addr = '127.0.0.1:9898' + +checkpoint_distance = 111 # in bytes +checkpoint_period = '111 s' + +gc_period = '222 s' +gc_horizon = 222 + +page_cache_size = 444 +max_file_descriptors = 333 + +# initial superuser role name to use when creating a new tenant +initial_superuser_name = 'zzzz' + + "#; + + #[test] + fn parse_defaults() -> anyhow::Result<()> { + let tempdir = tempdir()?; + let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?; + // we have to create dummy pathes to overcome the validation errors + let config_string = format!("pg_distrib_dir='{}'", pg_distrib_dir.display()); + let toml = config_string.parse()?; + + let parsed_config = + PageServerConf::parse_and_validate(&toml, &workdir).unwrap_or_else(|e| { + panic!("Failed to parse config '{}', reason: {}", config_string, e) + }); + + assert_eq!( + parsed_config, + PageServerConf { + listen_pg_addr: defaults::DEFAULT_PG_LISTEN_ADDR.to_string(), + listen_http_addr: defaults::DEFAULT_HTTP_LISTEN_ADDR.to_string(), + checkpoint_distance: defaults::DEFAULT_CHECKPOINT_DISTANCE, + checkpoint_period: humantime::parse_duration(defaults::DEFAULT_CHECKPOINT_PERIOD)?, + gc_horizon: defaults::DEFAULT_GC_HORIZON, + gc_period: humantime::parse_duration(defaults::DEFAULT_GC_PERIOD)?, + superuser: defaults::DEFAULT_SUPERUSER.to_string(), + page_cache_size: defaults::DEFAULT_PAGE_CACHE_SIZE, + max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS, + workdir, + pg_distrib_dir, + auth_type: AuthType::Trust, + auth_validation_public_key_path: None, + remote_storage_config: None, + }, + "Correct defaults should be used when no config values are provided" + ); + + Ok(()) + } + + #[test] + fn parse_basic_config() -> anyhow::Result<()> { + let tempdir = tempdir()?; + let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?; + + let config_string = format!( + "{}pg_distrib_dir='{}'", + ALL_BASE_VALUES_TOML, + pg_distrib_dir.display() + ); + let toml = config_string.parse()?; + + let parsed_config = + PageServerConf::parse_and_validate(&toml, &workdir).unwrap_or_else(|e| { + panic!("Failed to parse config '{}', reason: {}", config_string, e) + }); + + assert_eq!( + parsed_config, + PageServerConf { + listen_pg_addr: "127.0.0.1:64000".to_string(), + listen_http_addr: "127.0.0.1:9898".to_string(), + checkpoint_distance: 111, + checkpoint_period: Duration::from_secs(111), + gc_horizon: 222, + gc_period: Duration::from_secs(222), + superuser: "zzzz".to_string(), + page_cache_size: 444, + max_file_descriptors: 333, + workdir, + pg_distrib_dir, + auth_type: AuthType::Trust, + auth_validation_public_key_path: None, + remote_storage_config: None, + }, + "Should be able to parse all basic config values correctly" + ); + + Ok(()) + } + + #[test] + fn parse_remote_fs_storage_config() -> anyhow::Result<()> { + let tempdir = tempdir()?; + let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?; + + let local_storage_path = tempdir.path().join("local_remote_storage"); + + let identical_toml_declarations = &[ + format!( + r#"[remote_storage] +local_path = '{}'"#, + local_storage_path.display() + ), + format!( + "remote_storage={{local_path='{}'}}", + local_storage_path.display() + ), + ]; + + for remote_storage_config_str in identical_toml_declarations { + let config_string = format!( + r#"{} +pg_distrib_dir='{}' + +{}"#, + ALL_BASE_VALUES_TOML, + pg_distrib_dir.display(), + remote_storage_config_str, + ); + + let toml = config_string.parse()?; + + let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir) + .unwrap_or_else(|e| { + panic!("Failed to parse config '{}', reason: {}", config_string, e) + }) + .remote_storage_config + .expect("Should have remote storage config for the local FS"); + + assert_eq!( + parsed_remote_storage_config, + RemoteStorageConfig { + max_concurrent_sync: NonZeroUsize::new( + defaults::DEFAULT_REMOTE_STORAGE_MAX_CONCURRENT_SYNC + ) + .unwrap(), + max_sync_errors: NonZeroU32::new(defaults::DEFAULT_REMOTE_STORAGE_MAX_SYNC_ERRORS) + .unwrap(), + storage: RemoteStorageKind::LocalFs(local_storage_path.clone()), + }, + "Remote storage config should correctly parse the local FS config and fill other storage defaults" + ); + } + Ok(()) + } + + #[test] + fn parse_remote_s3_storage_config() -> anyhow::Result<()> { + let tempdir = tempdir()?; + let (workdir, pg_distrib_dir) = prepare_fs(&tempdir)?; + + let bucket_name = "some-sample-bucket".to_string(); + let bucket_region = "eu-north-1".to_string(); + let access_key_id = "SOMEKEYAAAAASADSAH*#".to_string(); + let secret_access_key = "SOMEsEcReTsd292v".to_string(); + let max_concurrent_sync = NonZeroUsize::new(111).unwrap(); + let max_sync_errors = NonZeroU32::new(222).unwrap(); + + let identical_toml_declarations = &[ + format!( + r#"[remote_storage] +max_concurrent_sync = {} +max_sync_errors = {} +bucket_name = '{}' +bucket_region = '{}' +access_key_id = '{}' +secret_access_key = '{}'"#, + max_concurrent_sync, max_sync_errors, bucket_name, bucket_region, access_key_id, secret_access_key + ), + format!( + "remote_storage={{max_concurrent_sync = {}, max_sync_errors = {}, bucket_name='{}', bucket_region='{}', access_key_id='{}', secret_access_key='{}'}}", + max_concurrent_sync, max_sync_errors, bucket_name, bucket_region, access_key_id, secret_access_key + ), + ]; + + for remote_storage_config_str in identical_toml_declarations { + let config_string = format!( + r#"{} +pg_distrib_dir='{}' + +{}"#, + ALL_BASE_VALUES_TOML, + pg_distrib_dir.display(), + remote_storage_config_str, + ); + + let toml = config_string.parse()?; + + let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir) + .unwrap_or_else(|e| { + panic!("Failed to parse config '{}', reason: {}", config_string, e) + }) + .remote_storage_config + .expect("Should have remote storage config for S3"); + + assert_eq!( + parsed_remote_storage_config, + RemoteStorageConfig { + max_concurrent_sync, + max_sync_errors, + storage: RemoteStorageKind::AwsS3(S3Config { + bucket_name: bucket_name.clone(), + bucket_region: bucket_region.clone(), + access_key_id: Some(access_key_id.clone()), + secret_access_key: Some(secret_access_key.clone()), + }), + }, + "Remote storage config should correctly parse the S3 config" + ); + } + Ok(()) + } + + fn prepare_fs(tempdir: &TempDir) -> anyhow::Result<(PathBuf, PathBuf)> { + let tempdir_path = tempdir.path(); + + let workdir = tempdir_path.join("workdir"); + fs::create_dir_all(&workdir)?; + + let pg_distrib_dir = tempdir_path.join("pg_distrib"); + fs::create_dir_all(&pg_distrib_dir)?; + let postgres_bin_dir = pg_distrib_dir.join("bin"); + fs::create_dir_all(&postgres_bin_dir)?; + fs::write(postgres_bin_dir.join("postgres"), "I'm postgres, trust me")?; + + Ok((workdir, pg_distrib_dir)) + } +} diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 4f6824b20e..ecaeb3466f 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -26,7 +26,7 @@ use super::models::BranchCreateRequest; use super::models::TenantCreateRequest; use crate::branches::BranchInfo; use crate::repository::TimelineSyncState; -use crate::{branches, tenant_mgr, PageServerConf, ZTenantId}; +use crate::{branches, config::PageServerConf, tenant_mgr, ZTenantId}; #[derive(Debug)] struct State { diff --git a/pageserver/src/import_datadir.rs b/pageserver/src/import_datadir.rs index 6f980f96f8..d4e8b9ae72 100644 --- a/pageserver/src/import_datadir.rs +++ b/pageserver/src/import_datadir.rs @@ -322,7 +322,7 @@ fn import_wal( let mut offset = startpoint.segment_offset(pg_constants::WAL_SEGMENT_SIZE); let mut last_lsn = startpoint; - let mut walingest = WalIngest::new(writer, startpoint)?; + let mut walingest = WalIngest::new(writer.deref(), startpoint)?; while last_lsn <= endpoint { // FIXME: assume postgresql tli 1 for now diff --git a/pageserver/src/layered_repository.rs b/pageserver/src/layered_repository.rs index 7058b87e29..3df1bdd95d 100644 --- a/pageserver/src/layered_repository.rs +++ b/pageserver/src/layered_repository.rs @@ -32,6 +32,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant}; use self::metadata::{metadata_path, TimelineMetadata, METADATA_FILE_NAME}; +use crate::config::PageServerConf; use crate::page_cache; use crate::relish::*; use crate::remote_storage::{schedule_timeline_checkpoint_upload, schedule_timeline_download}; @@ -44,7 +45,6 @@ use crate::walreceiver; use crate::walreceiver::IS_WAL_RECEIVER; use crate::walredo::WalRedoManager; use crate::CheckpointConfig; -use crate::PageServerConf; use crate::{ZTenantId, ZTimelineId}; use zenith_metrics::{ diff --git a/pageserver/src/layered_repository/delta_layer.rs b/pageserver/src/layered_repository/delta_layer.rs index 208935df49..0a3b291b15 100644 --- a/pageserver/src/layered_repository/delta_layer.rs +++ b/pageserver/src/layered_repository/delta_layer.rs @@ -37,6 +37,7 @@ //! A detlta file is constructed using the 'bookfile' crate. Each file consists of two //! parts: the page versions and the segment sizes. They are stored as separate chapters. //! +use crate::config::PageServerConf; use crate::layered_repository::filename::{DeltaFileName, PathOrConf}; use crate::layered_repository::storage_layer::{ Layer, PageReconstructData, PageReconstructResult, PageVersion, SegmentBlk, SegmentTag, @@ -44,7 +45,6 @@ use crate::layered_repository::storage_layer::{ }; use crate::virtual_file::VirtualFile; use crate::walrecord; -use crate::PageServerConf; use crate::{ZTenantId, ZTimelineId}; use anyhow::{bail, ensure, Result}; use log::*; diff --git a/pageserver/src/layered_repository/ephemeral_file.rs b/pageserver/src/layered_repository/ephemeral_file.rs index 57fc12361f..d13e7ee62d 100644 --- a/pageserver/src/layered_repository/ephemeral_file.rs +++ b/pageserver/src/layered_repository/ephemeral_file.rs @@ -1,11 +1,11 @@ //! Implementation of append-only file data structure //! used to keep in-memory layers spilled on disk. +use crate::config::PageServerConf; use crate::page_cache; use crate::page_cache::PAGE_SZ; use crate::page_cache::{ReadBufResult, WriteBufResult}; use crate::virtual_file::VirtualFile; -use crate::PageServerConf; use lazy_static::lazy_static; use std::cmp::min; use std::collections::HashMap; diff --git a/pageserver/src/layered_repository/filename.rs b/pageserver/src/layered_repository/filename.rs index ea2652ecf3..df23700dfd 100644 --- a/pageserver/src/layered_repository/filename.rs +++ b/pageserver/src/layered_repository/filename.rs @@ -1,9 +1,9 @@ //! //! Helper functions for dealing with filenames of the image and delta layer files. //! +use crate::config::PageServerConf; use crate::layered_repository::storage_layer::SegmentTag; use crate::relish::*; -use crate::PageServerConf; use std::fmt; use std::path::PathBuf; diff --git a/pageserver/src/layered_repository/image_layer.rs b/pageserver/src/layered_repository/image_layer.rs index 50d4c3df30..c4565797d6 100644 --- a/pageserver/src/layered_repository/image_layer.rs +++ b/pageserver/src/layered_repository/image_layer.rs @@ -21,13 +21,13 @@ //! //! For non-blocky relishes, the image can be found in NONBLOCKY_IMAGE_CHAPTER. //! +use crate::config::PageServerConf; use crate::layered_repository::filename::{ImageFileName, PathOrConf}; use crate::layered_repository::storage_layer::{ Layer, PageReconstructData, PageReconstructResult, SegmentBlk, SegmentTag, }; use crate::layered_repository::RELISH_SEG_SIZE; use crate::virtual_file::VirtualFile; -use crate::PageServerConf; use crate::{ZTenantId, ZTimelineId}; use anyhow::{anyhow, bail, ensure, Context, Result}; use bytes::Bytes; diff --git a/pageserver/src/layered_repository/inmemory_layer.rs b/pageserver/src/layered_repository/inmemory_layer.rs index 37d672bb8e..13f6c51b90 100644 --- a/pageserver/src/layered_repository/inmemory_layer.rs +++ b/pageserver/src/layered_repository/inmemory_layer.rs @@ -4,6 +4,7 @@ //! //! And there's another BTreeMap to track the size of the relation. //! +use crate::config::PageServerConf; use crate::layered_repository::delta_layer::{DeltaLayer, DeltaLayerWriter}; use crate::layered_repository::ephemeral_file::EphemeralFile; use crate::layered_repository::filename::DeltaFileName; @@ -15,7 +16,6 @@ use crate::layered_repository::storage_layer::{ use crate::layered_repository::LayeredTimeline; use crate::layered_repository::ZERO_PAGE; use crate::repository::WALRecord; -use crate::PageServerConf; use crate::{ZTenantId, ZTimelineId}; use anyhow::{ensure, Result}; use bytes::Bytes; diff --git a/pageserver/src/layered_repository/layer_map.rs b/pageserver/src/layered_repository/layer_map.rs index 929932920d..99a7319be3 100644 --- a/pageserver/src/layered_repository/layer_map.rs +++ b/pageserver/src/layered_repository/layer_map.rs @@ -402,7 +402,7 @@ impl<'a> Iterator for HistoricLayerIter<'a> { #[cfg(test)] mod tests { use super::*; - use crate::PageServerConf; + use crate::config::PageServerConf; use std::str::FromStr; use zenith_utils::zid::{ZTenantId, ZTimelineId}; diff --git a/pageserver/src/layered_repository/metadata.rs b/pageserver/src/layered_repository/metadata.rs index 5c75ce5892..960a1b7fe3 100644 --- a/pageserver/src/layered_repository/metadata.rs +++ b/pageserver/src/layered_repository/metadata.rs @@ -15,7 +15,7 @@ use zenith_utils::{ zid::{ZTenantId, ZTimelineId}, }; -use crate::PageServerConf; +use crate::config::PageServerConf; // Taken from PG_CONTROL_MAX_SAFE_SIZE const METADATA_MAX_SAFE_SIZE: usize = 512; diff --git a/pageserver/src/layered_repository/page_versions.rs b/pageserver/src/layered_repository/page_versions.rs index cb8ff04439..9a1fa26eb9 100644 --- a/pageserver/src/layered_repository/page_versions.rs +++ b/pageserver/src/layered_repository/page_versions.rs @@ -195,7 +195,7 @@ mod tests { use bytes::Bytes; use super::*; - use crate::PageServerConf; + use crate::config::PageServerConf; use std::fs; use std::str::FromStr; use zenith_utils::zid::{ZTenantId, ZTimelineId}; diff --git a/pageserver/src/lib.rs b/pageserver/src/lib.rs index 9758b85381..b2ae78448a 100644 --- a/pageserver/src/lib.rs +++ b/pageserver/src/lib.rs @@ -1,16 +1,6 @@ -use layered_repository::TIMELINES_SEGMENT_NAME; -use zenith_utils::postgres_backend::AuthType; -use zenith_utils::zid::{ZTenantId, ZTimelineId}; - -use std::num::{NonZeroU32, NonZeroUsize}; -use std::path::PathBuf; -use std::time::Duration; - -use lazy_static::lazy_static; -use zenith_metrics::{register_int_gauge_vec, IntGaugeVec}; - pub mod basebackup; pub mod branches; +pub mod config; pub mod http; pub mod import_datadir; pub mod layered_repository; @@ -27,32 +17,9 @@ pub mod walreceiver; pub mod walrecord; pub mod walredo; -pub mod defaults { - use const_format::formatcp; - use std::time::Duration; - - pub const DEFAULT_PG_LISTEN_PORT: u16 = 64000; - pub const DEFAULT_PG_LISTEN_ADDR: &str = formatcp!("127.0.0.1:{DEFAULT_PG_LISTEN_PORT}"); - pub const DEFAULT_HTTP_LISTEN_PORT: u16 = 9898; - pub const DEFAULT_HTTP_LISTEN_ADDR: &str = formatcp!("127.0.0.1:{DEFAULT_HTTP_LISTEN_PORT}"); - - // FIXME: This current value is very low. I would imagine something like 1 GB or 10 GB - // would be more appropriate. But a low value forces the code to be exercised more, - // which is good for now to trigger bugs. - pub const DEFAULT_CHECKPOINT_DISTANCE: u64 = 256 * 1024 * 1024; - pub const DEFAULT_CHECKPOINT_PERIOD: Duration = Duration::from_secs(1); - - pub const DEFAULT_GC_HORIZON: u64 = 64 * 1024 * 1024; - pub const DEFAULT_GC_PERIOD: Duration = Duration::from_secs(100); - - pub const DEFAULT_SUPERUSER: &str = "zenith_admin"; - pub const DEFAULT_REMOTE_STORAGE_MAX_CONCURRENT_SYNC: usize = 100; - pub const DEFAULT_REMOTE_STORAGE_MAX_SYNC_ERRORS: u32 = 10; - - pub const DEFAULT_OPEN_MEM_LIMIT: usize = 128 * 1024 * 1024; - pub const DEFAULT_PAGE_CACHE_SIZE: usize = 8192; - pub const DEFAULT_MAX_FILE_DESCRIPTORS: usize = 100; -} +use lazy_static::lazy_static; +use zenith_metrics::{register_int_gauge_vec, IntGaugeVec}; +use zenith_utils::zid::{ZTenantId, ZTimelineId}; lazy_static! { static ref LIVE_CONNECTIONS_COUNT: IntGaugeVec = register_int_gauge_vec!( @@ -65,118 +32,6 @@ lazy_static! { pub const LOG_FILE_NAME: &str = "pageserver.log"; -#[derive(Debug, Clone)] -pub struct PageServerConf { - pub daemonize: bool, - pub listen_pg_addr: String, - pub listen_http_addr: String, - // Flush out an inmemory layer, if it's holding WAL older than this - // This puts a backstop on how much WAL needs to be re-digested if the - // page server crashes. - pub checkpoint_distance: u64, - pub checkpoint_period: Duration, - - pub gc_horizon: u64, - pub gc_period: Duration, - pub superuser: String, - - pub open_mem_limit: usize, - pub page_cache_size: usize, - pub max_file_descriptors: usize, - - // Repository directory, relative to current working directory. - // Normally, the page server changes the current working directory - // to the repository, and 'workdir' is always '.'. But we don't do - // that during unit testing, because the current directory is global - // to the process but different unit tests work on different - // repositories. - pub workdir: PathBuf, - - pub pg_distrib_dir: PathBuf, - - pub auth_type: AuthType, - - pub auth_validation_public_key_path: Option, - pub remote_storage_config: Option, -} - -impl PageServerConf { - // - // Repository paths, relative to workdir. - // - - fn tenants_path(&self) -> PathBuf { - self.workdir.join("tenants") - } - - fn tenant_path(&self, tenantid: &ZTenantId) -> PathBuf { - self.tenants_path().join(tenantid.to_string()) - } - - fn tags_path(&self, tenantid: &ZTenantId) -> PathBuf { - self.tenant_path(tenantid).join("refs").join("tags") - } - - fn tag_path(&self, tag_name: &str, tenantid: &ZTenantId) -> PathBuf { - self.tags_path(tenantid).join(tag_name) - } - - fn branches_path(&self, tenantid: &ZTenantId) -> PathBuf { - self.tenant_path(tenantid).join("refs").join("branches") - } - - fn branch_path(&self, branch_name: &str, tenantid: &ZTenantId) -> PathBuf { - self.branches_path(tenantid).join(branch_name) - } - - fn timelines_path(&self, tenantid: &ZTenantId) -> PathBuf { - self.tenant_path(tenantid).join(TIMELINES_SEGMENT_NAME) - } - - fn timeline_path(&self, timelineid: &ZTimelineId, tenantid: &ZTenantId) -> PathBuf { - self.timelines_path(tenantid).join(timelineid.to_string()) - } - - // - // Postgres distribution paths - // - - pub fn pg_bin_dir(&self) -> PathBuf { - self.pg_distrib_dir.join("bin") - } - - pub fn pg_lib_dir(&self) -> PathBuf { - self.pg_distrib_dir.join("lib") - } - - #[cfg(test)] - fn test_repo_dir(test_name: &str) -> PathBuf { - PathBuf::from(format!("../tmp_check/test_{}", test_name)) - } - - #[cfg(test)] - fn dummy_conf(repo_dir: PathBuf) -> Self { - PageServerConf { - daemonize: false, - checkpoint_distance: defaults::DEFAULT_CHECKPOINT_DISTANCE, - checkpoint_period: Duration::from_secs(10), - gc_horizon: defaults::DEFAULT_GC_HORIZON, - gc_period: Duration::from_secs(10), - open_mem_limit: defaults::DEFAULT_OPEN_MEM_LIMIT, - page_cache_size: defaults::DEFAULT_PAGE_CACHE_SIZE, - max_file_descriptors: defaults::DEFAULT_MAX_FILE_DESCRIPTORS, - listen_pg_addr: defaults::DEFAULT_PG_LISTEN_ADDR.to_string(), - listen_http_addr: defaults::DEFAULT_HTTP_LISTEN_ADDR.to_string(), - superuser: "zenith_admin".to_string(), - workdir: repo_dir, - pg_distrib_dir: "".into(), - auth_type: AuthType::Trust, - auth_validation_public_key_path: None, - remote_storage_config: None, - } - } -} - /// Config for the Repository checkpointer #[derive(Debug, Clone, Copy)] pub enum CheckpointConfig { @@ -185,50 +40,3 @@ pub enum CheckpointConfig { // Flush all in-memory data Forced, } - -/// External backup storage configuration, enough for creating a client for that storage. -#[derive(Debug, Clone)] -pub struct RemoteStorageConfig { - /// Max allowed number of concurrent sync operations between pageserver and the remote storage. - pub max_concurrent_sync: NonZeroUsize, - /// Max allowed errors before the sync task is considered failed and evicted. - pub max_sync_errors: NonZeroU32, - /// The storage connection configuration. - pub storage: RemoteStorageKind, -} - -/// A kind of a remote storage to connect to, with its connection configuration. -#[derive(Debug, Clone)] -pub enum RemoteStorageKind { - /// Storage based on local file system. - /// Specify a root folder to place all stored relish data into. - LocalFs(PathBuf), - /// AWS S3 based storage, storing all relishes into the root - /// of the S3 bucket from the config. - AwsS3(S3Config), -} - -/// AWS S3 bucket coordinates and access credentials to manage the bucket contents (read and write). -#[derive(Clone)] -pub struct S3Config { - /// Name of the bucket to connect to. - pub bucket_name: String, - /// The region where the bucket is located at. - pub bucket_region: String, - /// "Login" to use when connecting to bucket. - /// Can be empty for cases like AWS k8s IAM - /// where we can allow certain pods to connect - /// to the bucket directly without any credentials. - pub access_key_id: Option, - /// "Password" to use when connecting to bucket. - pub secret_access_key: Option, -} - -impl std::fmt::Debug for S3Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("S3Config") - .field("bucket_name", &self.bucket_name) - .field("bucket_region", &self.bucket_region) - .finish() - } -} diff --git a/pageserver/src/page_cache.rs b/pageserver/src/page_cache.rs index 525448b71f..b0c8d3a5d7 100644 --- a/pageserver/src/page_cache.rs +++ b/pageserver/src/page_cache.rs @@ -53,7 +53,7 @@ use zenith_utils::{ }; use crate::layered_repository::writeback_ephemeral_file; -use crate::{relish::RelTag, PageServerConf}; +use crate::{config::PageServerConf, relish::RelTag}; static PAGE_CACHE: OnceCell = OnceCell::new(); const TEST_PAGE_CACHE_SIZE: usize = 10; diff --git a/pageserver/src/page_service.rs b/pageserver/src/page_service.rs index bcecbebc61..d80e23022a 100644 --- a/pageserver/src/page_service.rs +++ b/pageserver/src/page_service.rs @@ -35,11 +35,11 @@ use zenith_utils::zid::{ZTenantId, ZTimelineId}; use crate::basebackup; use crate::branches; +use crate::config::PageServerConf; use crate::relish::*; use crate::repository::Timeline; use crate::tenant_mgr; use crate::walreceiver; -use crate::PageServerConf; // Wrapped in libpq CopyData enum PagestreamFeMessage { diff --git a/pageserver/src/remote_storage.rs b/pageserver/src/remote_storage.rs index d6f449c4dc..366ed16761 100644 --- a/pageserver/src/remote_storage.rs +++ b/pageserver/src/remote_storage.rs @@ -100,9 +100,9 @@ use zenith_utils::zid::{ZTenantId, ZTimelineId}; pub use self::storage_sync::{schedule_timeline_checkpoint_upload, schedule_timeline_download}; use self::{local_fs::LocalFs, rust_s3::S3}; use crate::{ + config::{PageServerConf, RemoteStorageKind}, layered_repository::metadata::{TimelineMetadata, METADATA_FILE_NAME}, repository::TimelineSyncState, - PageServerConf, RemoteStorageKind, }; /// Any timeline has its own id and its own tenant it belongs to, diff --git a/pageserver/src/remote_storage/rust_s3.rs b/pageserver/src/remote_storage/rust_s3.rs index a494d187e6..5fd5a88c8d 100644 --- a/pageserver/src/remote_storage/rust_s3.rs +++ b/pageserver/src/remote_storage/rust_s3.rs @@ -9,8 +9,8 @@ use s3::{bucket::Bucket, creds::Credentials, region::Region}; use tokio::io::{self, AsyncWriteExt}; use crate::{ + config::S3Config, remote_storage::{strip_path_prefix, RemoteStorage}, - S3Config, }; const S3_FILE_SEPARATOR: char = '/'; diff --git a/pageserver/src/remote_storage/storage_sync.rs b/pageserver/src/remote_storage/storage_sync.rs index a8f46ef511..5a52eaf29b 100644 --- a/pageserver/src/remote_storage/storage_sync.rs +++ b/pageserver/src/remote_storage/storage_sync.rs @@ -104,9 +104,9 @@ use self::{ }; use super::{RemoteStorage, SyncStartupData, TimelineSyncId}; use crate::{ - layered_repository::metadata::TimelineMetadata, + config::PageServerConf, layered_repository::metadata::TimelineMetadata, remote_storage::storage_sync::compression::read_archive_header, repository::TimelineSyncState, - tenant_mgr::set_timeline_states, PageServerConf, + tenant_mgr::set_timeline_states, }; use zenith_metrics::{register_histogram_vec, register_int_gauge, HistogramVec, IntGauge}; diff --git a/pageserver/src/remote_storage/storage_sync/download.rs b/pageserver/src/remote_storage/storage_sync/download.rs index 3494138cb5..a067bddd23 100644 --- a/pageserver/src/remote_storage/storage_sync/download.rs +++ b/pageserver/src/remote_storage/storage_sync/download.rs @@ -10,6 +10,7 @@ use tracing::{debug, error, trace, warn}; use zenith_utils::{lsn::Lsn, zid::ZTenantId}; use crate::{ + config::PageServerConf, layered_repository::metadata::{metadata_path, TimelineMetadata}, remote_storage::{ storage_sync::{ @@ -18,7 +19,6 @@ use crate::{ }, RemoteStorage, TimelineSyncId, }, - PageServerConf, }; use super::{ diff --git a/pageserver/src/remote_storage/storage_sync/index.rs b/pageserver/src/remote_storage/storage_sync/index.rs index e5906264af..f22471dbbe 100644 --- a/pageserver/src/remote_storage/storage_sync/index.rs +++ b/pageserver/src/remote_storage/storage_sync/index.rs @@ -18,12 +18,12 @@ use zenith_utils::{ }; use crate::{ + config::PageServerConf, layered_repository::TIMELINES_SEGMENT_NAME, remote_storage::{ storage_sync::compression::{parse_archive_name, FileEntry}, TimelineSyncId, }, - PageServerConf, }; use super::compression::ArchiveHeader; diff --git a/pageserver/src/remote_storage/storage_sync/upload.rs b/pageserver/src/remote_storage/storage_sync/upload.rs index 702b84727a..a273b05293 100644 --- a/pageserver/src/remote_storage/storage_sync/upload.rs +++ b/pageserver/src/remote_storage/storage_sync/upload.rs @@ -10,6 +10,7 @@ use tracing::{debug, error, warn}; use zenith_utils::zid::ZTenantId; use crate::{ + config::PageServerConf, remote_storage::{ storage_sync::{ compression, @@ -18,7 +19,6 @@ use crate::{ }, RemoteStorage, TimelineSyncId, }, - PageServerConf, }; use super::{compression::ArchiveHeader, index::RemoteTimelineIndex, NewCheckpoint}; diff --git a/pageserver/src/repository.rs b/pageserver/src/repository.rs index 74f2969ecd..4bed6449ed 100644 --- a/pageserver/src/repository.rs +++ b/pageserver/src/repository.rs @@ -304,9 +304,9 @@ pub mod repo_harness { use std::{fs, path::PathBuf}; use crate::{ + config::PageServerConf, layered_repository::{LayeredRepository, TIMELINES_SEGMENT_NAME}, walredo::{WalRedoError, WalRedoManager}, - PageServerConf, }; use super::*; diff --git a/pageserver/src/tenant_mgr.rs b/pageserver/src/tenant_mgr.rs index ad0be32fa9..884ef09886 100644 --- a/pageserver/src/tenant_mgr.rs +++ b/pageserver/src/tenant_mgr.rs @@ -2,11 +2,11 @@ //! page server. use crate::branches; +use crate::config::PageServerConf; use crate::layered_repository::LayeredRepository; use crate::repository::{Repository, Timeline, TimelineSyncState}; use crate::tenant_threads; use crate::walredo::PostgresRedoManager; -use crate::PageServerConf; use anyhow::{anyhow, bail, Context, Result}; use lazy_static::lazy_static; use log::*; diff --git a/pageserver/src/tenant_threads.rs b/pageserver/src/tenant_threads.rs index c951eab917..afcd313ea1 100644 --- a/pageserver/src/tenant_threads.rs +++ b/pageserver/src/tenant_threads.rs @@ -1,9 +1,9 @@ //! This module contains functions to serve per-tenant background processes, //! such as checkpointer and GC +use crate::config::PageServerConf; use crate::tenant_mgr; use crate::tenant_mgr::TenantState; use crate::CheckpointConfig; -use crate::PageServerConf; use anyhow::Result; use lazy_static::lazy_static; use std::collections::HashMap; diff --git a/pageserver/src/virtual_file.rs b/pageserver/src/virtual_file.rs index 24cef26169..9fd71d1ce2 100644 --- a/pageserver/src/virtual_file.rs +++ b/pageserver/src/virtual_file.rs @@ -469,7 +469,7 @@ mod tests { FD: Read + Write + Seek + FileExt, OF: Fn(&Path, &OpenOptions) -> Result, { - let testdir = crate::PageServerConf::test_repo_dir(testname); + let testdir = crate::config::PageServerConf::test_repo_dir(testname); std::fs::create_dir_all(&testdir)?; let path_a = testdir.join("file_a"); @@ -571,7 +571,7 @@ mod tests { const THREADS: usize = 100; const SAMPLE: [u8; SIZE] = [0xADu8; SIZE]; - let testdir = crate::PageServerConf::test_repo_dir("vfile_concurrency"); + let testdir = crate::config::PageServerConf::test_repo_dir("vfile_concurrency"); std::fs::create_dir_all(&testdir)?; // Create a test file. diff --git a/pageserver/src/walreceiver.rs b/pageserver/src/walreceiver.rs index 148b862d5b..c5aef7be60 100644 --- a/pageserver/src/walreceiver.rs +++ b/pageserver/src/walreceiver.rs @@ -5,11 +5,11 @@ //! //! We keep one WAL receiver active per timeline. +use crate::config::PageServerConf; use crate::tenant_mgr; use crate::tenant_mgr::TenantState; use crate::tenant_threads; use crate::walingest::WalIngest; -use crate::PageServerConf; use anyhow::{bail, Context, Error, Result}; use lazy_static::lazy_static; use postgres::fallible_iterator::FallibleIterator; diff --git a/pageserver/src/walredo.rs b/pageserver/src/walredo.rs index 2a0ef37f4a..a8e25d3478 100644 --- a/pageserver/src/walredo.rs +++ b/pageserver/src/walredo.rs @@ -41,11 +41,11 @@ use zenith_utils::lsn::Lsn; use zenith_utils::nonblock::set_nonblock; use zenith_utils::zid::ZTenantId; +use crate::config::PageServerConf; use crate::relish::*; use crate::repository::WALRecord; use crate::walrecord::XlMultiXactCreate; use crate::walrecord::XlXactParsedRecord; -use crate::PageServerConf; use postgres_ffi::nonrelfile_utils::mx_offset_to_flags_bitshift; use postgres_ffi::nonrelfile_utils::mx_offset_to_flags_offset; use postgres_ffi::nonrelfile_utils::mx_offset_to_member_offset; diff --git a/test_runner/fixtures/zenith_fixtures.py b/test_runner/fixtures/zenith_fixtures.py index 445532ff33..f581c8a3ee 100644 --- a/test_runner/fixtures/zenith_fixtures.py +++ b/test_runner/fixtures/zenith_fixtures.py @@ -428,8 +428,8 @@ default_tenantid = '{self.initial_tenant}' toml += f""" [pageserver] -pg_port = {pageserver_port.pg} -http_port = {pageserver_port.http} +listen_pg_addr = 'localhost:{pageserver_port.pg}' +listen_http_addr = 'localhost:{pageserver_port.http}' auth_type = '{pageserver_auth_type}' """ diff --git a/zenith/src/main.rs b/zenith/src/main.rs index 00e3648b99..02a99b1c2a 100644 --- a/zenith/src/main.rs +++ b/zenith/src/main.rs @@ -6,9 +6,9 @@ use control_plane::local_env; use control_plane::local_env::LocalEnv; use control_plane::safekeeper::SafekeeperNode; use control_plane::storage::PageServerNode; -use pageserver::defaults::{ - DEFAULT_HTTP_LISTEN_PORT as DEFAULT_PAGESERVER_HTTP_PORT, - DEFAULT_PG_LISTEN_PORT as DEFAULT_PAGESERVER_PG_PORT, +use pageserver::config::defaults::{ + DEFAULT_HTTP_LISTEN_ADDR as DEFAULT_PAGESERVER_HTTP_ADDR, + DEFAULT_PG_LISTEN_ADDR as DEFAULT_PAGESERVER_PG_ADDR, }; use std::collections::HashMap; use std::process::exit; @@ -32,8 +32,8 @@ fn default_conf() -> String { r#" # Default built-in configuration, defined in main.rs [pageserver] -pg_port = {pageserver_pg_port} -http_port = {pageserver_http_port} +listen_pg_addr = {pageserver_pg_addr} +listen_http_addr = {pageserver_http_addr} auth_type = '{pageserver_auth_type}' [[safekeepers]] @@ -41,8 +41,8 @@ name = '{safekeeper_name}' pg_port = {safekeeper_pg_port} http_port = {safekeeper_http_port} "#, - pageserver_pg_port = DEFAULT_PAGESERVER_PG_PORT, - pageserver_http_port = DEFAULT_PAGESERVER_HTTP_PORT, + pageserver_pg_addr = DEFAULT_PAGESERVER_PG_ADDR, + pageserver_http_addr = DEFAULT_PAGESERVER_HTTP_ADDR, pageserver_auth_type = AuthType::Trust, safekeeper_name = DEFAULT_SAFEKEEPER_NAME, safekeeper_pg_port = DEFAULT_SAFEKEEPER_PG_PORT, diff --git a/zenith_utils/src/postgres_backend.rs b/zenith_utils/src/postgres_backend.rs index 7e01832510..37ecc74c41 100644 --- a/zenith_utils/src/postgres_backend.rs +++ b/zenith_utils/src/postgres_backend.rs @@ -59,7 +59,7 @@ pub enum ProtoState { Established, } -#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub enum AuthType { Trust, MD5,