Compare commits

...

34 Commits

Author SHA1 Message Date
Christian Schwarz
b1e21c7705 Add trailing dot 2024-05-05 17:17:42 +00:00
Christian Schwarz
004af53035 git diff reduction & polish 2024-05-05 17:15:09 +00:00
Christian Schwarz
d8702dd819 Merge branch 'problame/test-suite-narrow-pageserver-config-override' into problame/remove-pageserver-config-overrides 2024-05-05 16:57:51 +00:00
Christian Schwarz
5f04224817 remove NEON_PAGESERVER_OVERRIDES env var 2024-05-05 16:53:36 +00:00
Christian Schwarz
9c547da6a6 reduce scope of this PR & fix naming 2024-05-05 16:49:20 +00:00
Christian Schwarz
6d343feef0 whitespace diff reduction 2024-05-05 16:20:58 +00:00
Christian Schwarz
f73c8c6bd6 Merge branch 'problame/test-suite-narrow-pageserver-config-override' into problame/remove-pageserver-config-overrides
Conflicts:
	control_plane/src/pageserver.rs
    => pick ours
2024-05-05 16:19:11 +00:00
Christian Schwarz
51224c84c2 even more diff reduction 2024-05-05 16:16:59 +00:00
Christian Schwarz
6bccd64514 miimize diff by re-adding whitespace 2024-05-05 15:59:05 +00:00
Christian Schwarz
28c95e4207 Merge branch 'problame/test-suite-narrow-pageserver-config-override' into problame/remove-pageserver-config-overrides 2024-05-04 16:15:45 +00:00
Christian Schwarz
aacf8110a0 pretty up the inlined override code, long option --config-override 2024-05-04 16:12:50 +00:00
Christian Schwarz
70977afd07 ruff check & format 2024-05-04 15:14:34 +00:00
Christian Schwarz
7363b44b50 neon_local: remove --pageserver-config-overrides, neon_local init takes a toml tempfile 2024-05-04 15:13:28 +00:00
Christian Schwarz
cc64e1b17f Merge branch 'problame/test-suite-narrow-pageserver-config-override' into problame/remove-pageserver-config-overrides 2024-05-04 13:17:25 +00:00
Christian Schwarz
25dfafc2df undo the renaming, it's too much churn for review; will do in a separate PR 2024-05-04 13:14:41 +00:00
Christian Schwarz
d72fe6f5ee no neon_local_overrides during start(); inline it into PageServerNode::init 2024-05-04 13:10:45 +00:00
Christian Schwarz
0bca1a5de3 Revert "neon_local: only set --pageserver-config-override=remote_storage during init, not start"
This reverts commit 511f593360.
2024-05-04 12:33:02 +00:00
Christian Schwarz
511f593360 neon_local: only set --pageserver-config-override=remote_storage during init, not start 2024-05-04 12:30:06 +00:00
Christian Schwarz
b96e0b2458 rely on init to store remote storage config in pageserver.toml
This allows inlining append_pageserver_param_overrides into NeonCli.init()
2024-05-04 12:07:33 +00:00
Christian Schwarz
b4ed3b15b9 remove support for pageserver -c/--config-override and neon_local --pageserver-config-override 2024-05-04 11:50:59 +00:00
Christian Schwarz
ad185dd594 test_suite: remove usage of --pageserver-config-override
Rewrite the pageserver.toml instead.
2024-05-04 11:50:59 +00:00
Christian Schwarz
58055c7a96 remove NEON_PAGESERVER_OVERRIDES env var (no committed code uses it) 2024-05-04 11:50:59 +00:00
Christian Schwarz
ec04f0f4d4 Merge branch 'problame/remove-pageserver-update-config-flag' into problame/test-suite-narrow-pageserver-config-override 2024-05-04 11:48:12 +00:00
Christian Schwarz
a52b563b59 fixups 2024-05-04 11:47:38 +00:00
Christian Schwarz
89afba066c refactor(test_suite): rely less on --pageserver-config-override outside of neon_local init
The `NeonCli.init()` persists the non-default pageserver config values
for remote storage & `NeonEnvBuilder.pageserver_config_override` in
`pageserver.toml`.

We don't need to repeat them on each pageserver start after that.
2024-05-04 11:34:59 +00:00
Christian Schwarz
6bcb0959ad ruff format 2024-05-04 10:25:53 +00:00
Christian Schwarz
8f3051b416 Merge branch 'main' into problame/remove-pageserver-update-config-flag 2024-05-04 10:22:05 +00:00
Christian Schwarz
998dc6255e refactor(pageserver): remove --update-init flag 2024-05-04 10:20:41 +00:00
Christian Schwarz
700aa96770 Merge branch 'main' into problame/move-pageserver-config-into-api-crate 2024-05-03 15:20:24 +00:00
Christian Schwarz
4a72fe0908 add requested backward-compatibility test 2024-05-03 15:19:18 +00:00
Christian Schwarz
923cdff13d Merge branch 'main' into problame/move-pageserver-config-into-api-crate 2024-05-03 12:36:18 +00:00
Christian Schwarz
498edfc0ff use NodeMetadata struct for writing metadata.json from neon_local 2024-05-03 12:35:41 +00:00
Christian Schwarz
d2e2a88737 move NodeMetadata type to pageserver_api::config 2024-05-03 12:35:41 +00:00
Christian Schwarz
6f720eb38f create config module inside pageserver_api crate 2024-05-03 12:35:41 +00:00
13 changed files with 203 additions and 220 deletions

1
Cargo.lock generated
View File

@@ -1348,6 +1348,7 @@ dependencies = [
"tokio-postgres", "tokio-postgres",
"tokio-util", "tokio-util",
"toml", "toml",
"toml_edit",
"tracing", "tracing",
"url", "url",
"utils", "utils",

View File

@@ -28,6 +28,7 @@ serde_with.workspace = true
tar.workspace = true tar.workspace = true
thiserror.workspace = true thiserror.workspace = true
toml.workspace = true toml.workspace = true
toml_edit.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-postgres.workspace = true tokio-postgres.workspace = true
tokio-util.workspace = true tokio-util.workspace = true

View File

@@ -133,7 +133,7 @@ fn main() -> Result<()> {
let subcommand_result = match sub_name { let subcommand_result = match sub_name {
"tenant" => rt.block_on(handle_tenant(sub_args, &mut env)), "tenant" => rt.block_on(handle_tenant(sub_args, &mut env)),
"timeline" => rt.block_on(handle_timeline(sub_args, &mut env)), "timeline" => rt.block_on(handle_timeline(sub_args, &mut env)),
"start" => rt.block_on(handle_start_all(sub_args, &env)), "start" => rt.block_on(handle_start_all(&env)),
"stop" => rt.block_on(handle_stop_all(sub_args, &env)), "stop" => rt.block_on(handle_stop_all(sub_args, &env)),
"pageserver" => rt.block_on(handle_pageserver(sub_args, &env)), "pageserver" => rt.block_on(handle_pageserver(sub_args, &env)),
"storage_controller" => rt.block_on(handle_storage_controller(sub_args, &env)), "storage_controller" => rt.block_on(handle_storage_controller(sub_args, &env)),
@@ -358,6 +358,13 @@ fn handle_init(init_match: &ArgMatches) -> anyhow::Result<LocalEnv> {
default_conf(*num_pageservers) default_conf(*num_pageservers)
}; };
let pageserver_config: toml_edit::Document =
if let Some(path) = init_match.get_one::<PathBuf>("pageserver-config") {
std::fs::read_to_string(path)?.parse()?
} else {
toml_edit::Document::new()
};
let pg_version = init_match let pg_version = init_match
.get_one::<u32>("pg-version") .get_one::<u32>("pg-version")
.copied() .copied()
@@ -375,7 +382,7 @@ fn handle_init(init_match: &ArgMatches) -> anyhow::Result<LocalEnv> {
// Initialize pageserver, create initial tenant and timeline. // Initialize pageserver, create initial tenant and timeline.
for ps_conf in &env.pageservers { for ps_conf in &env.pageservers {
PageServerNode::from_env(&env, ps_conf) PageServerNode::from_env(&env, ps_conf)
.initialize(&pageserver_config_overrides(init_match)) .initialize(&pageserver_config)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
eprintln!("pageserver init failed: {e:?}"); eprintln!("pageserver init failed: {e:?}");
exit(1); exit(1);
@@ -397,15 +404,6 @@ fn get_default_pageserver(env: &local_env::LocalEnv) -> PageServerNode {
PageServerNode::from_env(env, ps_conf) PageServerNode::from_env(env, ps_conf)
} }
fn pageserver_config_overrides(init_match: &ArgMatches) -> Vec<&str> {
init_match
.get_many::<String>("pageserver-config-override")
.into_iter()
.flatten()
.map(String::as_str)
.collect()
}
async fn handle_tenant( async fn handle_tenant(
tenant_match: &ArgMatches, tenant_match: &ArgMatches,
env: &mut local_env::LocalEnv, env: &mut local_env::LocalEnv,
@@ -1068,10 +1066,7 @@ fn get_pageserver(env: &local_env::LocalEnv, args: &ArgMatches) -> Result<PageSe
async fn handle_pageserver(sub_match: &ArgMatches, env: &local_env::LocalEnv) -> Result<()> { async fn handle_pageserver(sub_match: &ArgMatches, env: &local_env::LocalEnv) -> Result<()> {
match sub_match.subcommand() { match sub_match.subcommand() {
Some(("start", subcommand_args)) => { Some(("start", subcommand_args)) => {
if let Err(e) = get_pageserver(env, subcommand_args)? if let Err(e) = get_pageserver(env, subcommand_args)?.start().await {
.start(&pageserver_config_overrides(subcommand_args))
.await
{
eprintln!("pageserver start failed: {e}"); eprintln!("pageserver start failed: {e}");
exit(1); exit(1);
} }
@@ -1097,10 +1092,7 @@ async fn handle_pageserver(sub_match: &ArgMatches, env: &local_env::LocalEnv) ->
exit(1); exit(1);
} }
if let Err(e) = pageserver if let Err(e) = pageserver.start().await {
.start(&pageserver_config_overrides(subcommand_args))
.await
{
eprintln!("pageserver start failed: {e}"); eprintln!("pageserver start failed: {e}");
exit(1); exit(1);
} }
@@ -1227,7 +1219,7 @@ async fn handle_safekeeper(sub_match: &ArgMatches, env: &local_env::LocalEnv) ->
Ok(()) Ok(())
} }
async fn handle_start_all(sub_match: &ArgMatches, env: &local_env::LocalEnv) -> anyhow::Result<()> { async fn handle_start_all(env: &local_env::LocalEnv) -> anyhow::Result<()> {
// Endpoints are not started automatically // Endpoints are not started automatically
broker::start_broker_process(env).await?; broker::start_broker_process(env).await?;
@@ -1244,10 +1236,7 @@ async fn handle_start_all(sub_match: &ArgMatches, env: &local_env::LocalEnv) ->
for ps_conf in &env.pageservers { for ps_conf in &env.pageservers {
let pageserver = PageServerNode::from_env(env, ps_conf); let pageserver = PageServerNode::from_env(env, ps_conf);
if let Err(e) = pageserver if let Err(e) = pageserver.start().await {
.start(&pageserver_config_overrides(sub_match))
.await
{
eprintln!("pageserver {} start failed: {:#}", ps_conf.id, e); eprintln!("pageserver {} start failed: {:#}", ps_conf.id, e);
try_stop_all(env, true).await; try_stop_all(env, true).await;
exit(1); exit(1);
@@ -1388,13 +1377,6 @@ fn cli() -> Command {
.required(false) .required(false)
.value_name("stop-mode"); .value_name("stop-mode");
let pageserver_config_args = Arg::new("pageserver-config-override")
.long("pageserver-config-override")
.num_args(1)
.action(ArgAction::Append)
.help("Additional pageserver's configuration options or overrides, refer to pageserver's 'config-override' CLI parameter docs for more")
.required(false);
let remote_ext_config_args = Arg::new("remote-ext-config") let remote_ext_config_args = Arg::new("remote-ext-config")
.long("remote-ext-config") .long("remote-ext-config")
.num_args(1) .num_args(1)
@@ -1450,14 +1432,21 @@ fn cli() -> Command {
.subcommand( .subcommand(
Command::new("init") Command::new("init")
.about("Initialize a new Neon repository, preparing configs for services to start with") .about("Initialize a new Neon repository, preparing configs for services to start with")
.arg(pageserver_config_args.clone())
.arg(num_pageservers_arg.clone()) .arg(num_pageservers_arg.clone())
.arg( .arg(
Arg::new("config") Arg::new("config")
.long("config") .long("config")
.required(false) .required(false)
.value_parser(value_parser!(PathBuf)) .value_parser(value_parser!(PathBuf))
.value_name("config"), .value_name("config")
)
.arg(
Arg::new("pageserver-config")
.long("pageserver-config")
.required(false)
.value_parser(value_parser!(PathBuf))
.value_name("pageserver-config")
.help("Merge the provided pageserver config into the one generated by neon_local."),
) )
.arg(pg_version_arg.clone()) .arg(pg_version_arg.clone())
.arg(force_arg) .arg(force_arg)
@@ -1539,7 +1528,6 @@ fn cli() -> Command {
.subcommand(Command::new("status")) .subcommand(Command::new("status"))
.subcommand(Command::new("start") .subcommand(Command::new("start")
.about("Start local pageserver") .about("Start local pageserver")
.arg(pageserver_config_args.clone())
) )
.subcommand(Command::new("stop") .subcommand(Command::new("stop")
.about("Stop local pageserver") .about("Stop local pageserver")
@@ -1547,7 +1535,6 @@ fn cli() -> Command {
) )
.subcommand(Command::new("restart") .subcommand(Command::new("restart")
.about("Restart local pageserver") .about("Restart local pageserver")
.arg(pageserver_config_args.clone())
) )
) )
.subcommand( .subcommand(
@@ -1660,7 +1647,6 @@ fn cli() -> Command {
.subcommand( .subcommand(
Command::new("start") Command::new("start")
.about("Start page server and safekeepers") .about("Start page server and safekeepers")
.arg(pageserver_config_args)
) )
.subcommand( .subcommand(
Command::new("stop") Command::new("stop")

View File

@@ -4,7 +4,6 @@
//! //!
//! .neon/ //! .neon/
//! //!
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
@@ -77,7 +76,7 @@ impl PageServerNode {
/// Merge overrides provided by the user on the command line with our default overides derived from neon_local configuration. /// Merge overrides provided by the user on the command line with our default overides derived from neon_local configuration.
/// ///
/// These all end up on the command line of the `pageserver` binary. /// These all end up on the command line of the `pageserver` binary.
fn neon_local_overrides(&self, cli_overrides: &[&str]) -> Vec<String> { fn neon_local_overrides(&self, cli_overrides: &toml_edit::Document) -> Vec<String> {
// FIXME: the paths should be shell-escaped to handle paths with spaces, quotas etc. // FIXME: the paths should be shell-escaped to handle paths with spaces, quotas etc.
let pg_distrib_dir_param = format!( let pg_distrib_dir_param = format!(
"pg_distrib_dir='{}'", "pg_distrib_dir='{}'",
@@ -157,10 +156,7 @@ impl PageServerNode {
} }
} }
if !cli_overrides if !cli_overrides.contains_key("remote_storage") {
.iter()
.any(|c| c.starts_with("remote_storage"))
{
overrides.push(format!( overrides.push(format!(
"remote_storage={{local_path='../{PAGESERVER_REMOTE_STORAGE_DIR}'}}" "remote_storage={{local_path='../{PAGESERVER_REMOTE_STORAGE_DIR}'}}"
)); ));
@@ -173,13 +169,13 @@ impl PageServerNode {
} }
// Apply the user-provided overrides // Apply the user-provided overrides
overrides.extend(cli_overrides.iter().map(|&c| c.to_owned())); overrides.push(cli_overrides.to_string());
overrides overrides
} }
/// Initializes a pageserver node by creating its config with the overrides provided. /// Initializes a pageserver node by creating its config with the overrides provided.
pub fn initialize(&self, config_overrides: &[&str]) -> anyhow::Result<()> { pub fn initialize(&self, config_overrides: &toml_edit::Document) -> anyhow::Result<()> {
// First, run `pageserver --init` and wait for it to write a config into FS and exit. // First, run `pageserver --init` and wait for it to write a config into FS and exit.
self.pageserver_init(config_overrides) self.pageserver_init(config_overrides)
.with_context(|| format!("Failed to run init for pageserver node {}", self.conf.id)) .with_context(|| format!("Failed to run init for pageserver node {}", self.conf.id))
@@ -197,11 +193,11 @@ impl PageServerNode {
.expect("non-Unicode path") .expect("non-Unicode path")
} }
pub async fn start(&self, config_overrides: &[&str]) -> anyhow::Result<()> { pub async fn start(&self) -> anyhow::Result<()> {
self.start_node(config_overrides, false).await self.start_node().await
} }
fn pageserver_init(&self, config_overrides: &[&str]) -> anyhow::Result<()> { fn pageserver_init(&self, config_overrides: &toml_edit::Document) -> anyhow::Result<()> {
let datadir = self.repo_path(); let datadir = self.repo_path();
let node_id = self.conf.id; let node_id = self.conf.id;
println!( println!(
@@ -219,11 +215,18 @@ impl PageServerNode {
let datadir_path_str = datadir.to_str().with_context(|| { let datadir_path_str = datadir.to_str().with_context(|| {
format!("Cannot start pageserver node {node_id} in path that has no string representation: {datadir:?}") format!("Cannot start pageserver node {node_id} in path that has no string representation: {datadir:?}")
})?; })?;
let mut args = self.pageserver_basic_args(config_overrides, datadir_path_str);
args.push(Cow::Borrowed("--init"));
// `pageserver --init` merges the `--config-override`s into a built-in default config,
// then writes out the merged product to `pageserver.toml`.
// TODO: just write the full `pageserver.toml` and get rid of `--config-override`.
let mut args = vec!["--init", "--workdir", datadir_path_str];
let overrides = self.neon_local_overrides(config_overrides);
for piece in &overrides {
args.push("--config-override");
args.push(piece);
}
let init_output = Command::new(self.env.pageserver_bin()) let init_output = Command::new(self.env.pageserver_bin())
.args(args.iter().map(Cow::as_ref)) .args(args)
.envs(self.pageserver_env_variables()?) .envs(self.pageserver_env_variables()?)
.output() .output()
.with_context(|| format!("Failed to run pageserver init for node {node_id}"))?; .with_context(|| format!("Failed to run pageserver init for node {node_id}"))?;
@@ -262,11 +265,7 @@ impl PageServerNode {
Ok(()) Ok(())
} }
async fn start_node( async fn start_node(&self) -> anyhow::Result<()> {
&self,
config_overrides: &[&str],
update_config: bool,
) -> anyhow::Result<()> {
// TODO: using a thread here because start_process() is not async but we need to call check_status() // TODO: using a thread here because start_process() is not async but we need to call check_status()
let datadir = self.repo_path(); let datadir = self.repo_path();
print!( print!(
@@ -283,15 +282,12 @@ impl PageServerNode {
self.conf.id, datadir, self.conf.id, datadir,
) )
})?; })?;
let mut args = self.pageserver_basic_args(config_overrides, datadir_path_str); let args = vec!["-D", datadir_path_str];
if update_config {
args.push(Cow::Borrowed("--update-config"));
}
background_process::start_process( background_process::start_process(
"pageserver", "pageserver",
&datadir, &datadir,
&self.env.pageserver_bin(), &self.env.pageserver_bin(),
args.iter().map(Cow::as_ref), args,
self.pageserver_env_variables()?, self.pageserver_env_variables()?,
background_process::InitialPidFile::Expect(self.pid_file()), background_process::InitialPidFile::Expect(self.pid_file()),
|| async { || async {
@@ -308,22 +304,6 @@ impl PageServerNode {
Ok(()) Ok(())
} }
fn pageserver_basic_args<'a>(
&self,
config_overrides: &'a [&'a str],
datadir_path_str: &'a str,
) -> Vec<Cow<'a, str>> {
let mut args = vec![Cow::Borrowed("-D"), Cow::Borrowed(datadir_path_str)];
let overrides = self.neon_local_overrides(config_overrides);
for config_override in overrides {
args.push(Cow::Borrowed("-c"));
args.push(Cow::Owned(config_override));
}
args
}
fn pageserver_env_variables(&self) -> anyhow::Result<Vec<(String, String)>> { fn pageserver_env_variables(&self) -> anyhow::Result<Vec<(String, String)>> {
// FIXME: why is this tied to pageserver's auth type? Whether or not the safekeeper // FIXME: why is this tied to pageserver's auth type? Whether or not the safekeeper
// needs a token, and how to generate that token, seems independent to whether // needs a token, and how to generate that token, seems independent to whether

View File

@@ -3,6 +3,7 @@
//! Main entry point for the Page Server executable. //! Main entry point for the Page Server executable.
use std::env::{var, VarError}; use std::env::{var, VarError};
use std::io::Read;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{env, ops::ControlFlow, str::FromStr}; use std::{env, ops::ControlFlow, str::FromStr};
@@ -151,37 +152,34 @@ fn initialize_config(
workdir: &Utf8Path, workdir: &Utf8Path,
) -> anyhow::Result<ControlFlow<(), &'static PageServerConf>> { ) -> anyhow::Result<ControlFlow<(), &'static PageServerConf>> {
let init = arg_matches.get_flag("init"); let init = arg_matches.get_flag("init");
let update_config = init || arg_matches.get_flag("update-config");
let (mut toml, config_file_exists) = if cfg_file_path.is_file() { let file_contents: Option<toml_edit::Document> = match std::fs::File::open(cfg_file_path) {
if init { Ok(mut f) => {
anyhow::bail!( if init {
"Config file '{cfg_file_path}' already exists, cannot init it, use --update-config to update it", anyhow::bail!("config file already exists: {cfg_file_path}");
); }
let md = f.metadata().context("stat config file")?;
if md.is_file() {
let mut s = String::new();
f.read_to_string(&mut s).context("read config file")?;
Some(s.parse().context("parse config file toml")?)
} else {
anyhow::bail!("directory entry exists but is not a file: {cfg_file_path}");
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
Err(e) => {
anyhow::bail!("open pageserver config: {e}: {cfg_file_path}");
} }
// Supplement the CLI arguments with the config file
let cfg_file_contents = std::fs::read_to_string(cfg_file_path)
.with_context(|| format!("Failed to read pageserver config at '{cfg_file_path}'"))?;
(
cfg_file_contents
.parse::<toml_edit::Document>()
.with_context(|| {
format!("Failed to parse '{cfg_file_path}' as pageserver config")
})?,
true,
)
} else if cfg_file_path.exists() {
anyhow::bail!("Config file '{cfg_file_path}' exists but is not a regular file");
} else {
// We're initializing the tenant, so there's no config file yet
(
DEFAULT_CONFIG_FILE
.parse::<toml_edit::Document>()
.context("could not parse built-in config file")?,
false,
)
}; };
let mut effective_config = file_contents.unwrap_or_else(|| {
DEFAULT_CONFIG_FILE
.parse()
.expect("unit tests ensure this works")
});
// Patch with overrides from the command line
if let Some(values) = arg_matches.get_many::<String>("config-override") { if let Some(values) = arg_matches.get_many::<String>("config-override") {
for option_line in values { for option_line in values {
let doc = toml_edit::Document::from_str(option_line).with_context(|| { let doc = toml_edit::Document::from_str(option_line).with_context(|| {
@@ -189,22 +187,21 @@ fn initialize_config(
})?; })?;
for (key, item) in doc.iter() { for (key, item) in doc.iter() {
if config_file_exists && update_config && key == "id" && toml.contains_key(key) { effective_config.insert(key, item.clone());
anyhow::bail!("Pageserver config file exists at '{cfg_file_path}' and has node id already, it cannot be overridden");
}
toml.insert(key, item.clone());
} }
} }
} }
debug!("Resulting toml: {toml}"); debug!("Resulting toml: {effective_config}");
let conf = PageServerConf::parse_and_validate(&toml, workdir)
// Construct the runtime representation
let conf = PageServerConf::parse_and_validate(&effective_config, workdir)
.context("Failed to parse pageserver configuration")?; .context("Failed to parse pageserver configuration")?;
if update_config { if init {
info!("Writing pageserver config to '{cfg_file_path}'"); info!("Writing pageserver config to '{cfg_file_path}'");
std::fs::write(cfg_file_path, toml.to_string()) std::fs::write(cfg_file_path, effective_config.to_string())
.with_context(|| format!("Failed to write pageserver config to '{cfg_file_path}'"))?; .with_context(|| format!("Failed to write pageserver config to '{cfg_file_path}'"))?;
info!("Config successfully written to '{cfg_file_path}'") info!("Config successfully written to '{cfg_file_path}'")
} }
@@ -758,18 +755,13 @@ fn cli() -> Command {
// See `settings.md` for more details on the extra configuration patameters pageserver can process // See `settings.md` for more details on the extra configuration patameters pageserver can process
.arg( .arg(
Arg::new("config-override") Arg::new("config-override")
.long("config-override")
.short('c') .short('c')
.num_args(1) .num_args(1)
.action(ArgAction::Append) .action(ArgAction::Append)
.help("Additional configuration overrides of the ones from the toml config file (or new ones to add there). \ .help("Additional configuration overrides of the ones from the toml config file (or new ones to add there). \
Any option has to be a valid toml document, example: `-c=\"foo='hey'\"` `-c=\"foo={value=1}\"`"), Any option has to be a valid toml document, example: `-c=\"foo='hey'\"` `-c=\"foo={value=1}\"`"),
) )
.arg(
Arg::new("update-config")
.long("update-config")
.action(ArgAction::SetTrue)
.help("Update the config file when started"),
)
.arg( .arg(
Arg::new("enabled-features") Arg::new("enabled-features")
.long("enabled-features") .long("enabled-features")

View File

@@ -76,13 +76,10 @@ you can use `--pg-version` argument.
`TEST_OUTPUT`: Set the directory where test state and test output files `TEST_OUTPUT`: Set the directory where test state and test output files
should go. should go.
`TEST_SHARED_FIXTURES`: Try to re-use a single pageserver for all the tests. `TEST_SHARED_FIXTURES`: Try to re-use a single pageserver for all the tests.
`NEON_PAGESERVER_OVERRIDES`: add a `;`-separated set of configs that will be passed as
`RUST_LOG`: logging configuration to pass into Neon CLI `RUST_LOG`: logging configuration to pass into Neon CLI
Useful parameters and commands: Useful parameters and commands:
`--pageserver-config-override=${value}` `-c` values to pass into pageserver through neon_local cli
`--preserve-database-files` to preserve pageserver (layer) and safekeer (segment) timeline files on disk `--preserve-database-files` to preserve pageserver (layer) and safekeer (segment) timeline files on disk
after running a test suite. Such files might be large, so removed by default; but might be useful for debugging or creation of svg images with layer file contents. after running a test suite. Such files might be large, so removed by default; but might be useful for debugging or creation of svg images with layer file contents.

View File

@@ -14,7 +14,7 @@ import textwrap
import threading import threading
import time import time
import uuid import uuid
from contextlib import closing, contextmanager from contextlib import ExitStack, closing, contextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
@@ -450,6 +450,7 @@ class NeonEnvBuilder:
test_output_dir: Path, test_output_dir: Path,
test_overlay_dir: Optional[Path] = None, test_overlay_dir: Optional[Path] = None,
pageserver_remote_storage: Optional[RemoteStorage] = None, pageserver_remote_storage: Optional[RemoteStorage] = None,
# toml that will be decomposed into `--config-override` flags during `pageserver --init`
pageserver_config_override: Optional[str] = None, pageserver_config_override: Optional[str] = None,
num_safekeepers: int = 1, num_safekeepers: int = 1,
num_pageservers: int = 1, num_pageservers: int = 1,
@@ -1021,7 +1022,6 @@ class NeonEnv:
self.neon_local_binpath = config.neon_binpath self.neon_local_binpath = config.neon_binpath
self.pg_distrib_dir = config.pg_distrib_dir self.pg_distrib_dir = config.pg_distrib_dir
self.endpoint_counter = 0 self.endpoint_counter = 0
self.pageserver_config_override = config.pageserver_config_override
self.storage_controller_config = config.storage_controller_config self.storage_controller_config = config.storage_controller_config
# generate initial tenant ID here instead of letting 'neon init' generate it, # generate initial tenant ID here instead of letting 'neon init' generate it,
@@ -1131,7 +1131,11 @@ class NeonEnv:
cfg["safekeepers"].append(sk_cfg) cfg["safekeepers"].append(sk_cfg)
log.info(f"Config: {cfg}") log.info(f"Config: {cfg}")
self.neon_cli.init(cfg, force=config.config_init_force) self.neon_cli.init(
cfg,
force=config.config_init_force,
pageserver_config_override=config.pageserver_config_override,
)
def start(self): def start(self):
# Storage controller starts first, so that pageserver /re-attach calls don't # Storage controller starts first, so that pageserver /re-attach calls don't
@@ -1703,30 +1707,47 @@ class NeonCli(AbstractNeonCli):
self, self,
config: Dict[str, Any], config: Dict[str, Any],
force: Optional[str] = None, force: Optional[str] = None,
pageserver_config_override: Optional[str] = None,
) -> "subprocess.CompletedProcess[str]": ) -> "subprocess.CompletedProcess[str]":
with tempfile.NamedTemporaryFile(mode="w+") as tmp: remote_storage = self.env.pageserver_remote_storage
tmp.write(toml.dumps(config))
tmp.flush()
cmd = ["init", f"--config={tmp.name}", "--pg-version", self.env.pg_version] ps_config = {}
if remote_storage is not None:
remote_storage_toml_table = remote_storage_to_toml_inline_table(remote_storage)
ps_config["remote_storage"] = remote_storage_toml_table
if pageserver_config_override is not None:
for o in pageserver_config_override.split(";"):
override = toml.loads(o)
for key, value in override.items():
ps_config[key] = value
with ExitStack() as stack:
ps_config_file = stack.enter_context(tempfile.NamedTemporaryFile(mode="w+"))
ps_config_file.write(toml.dumps(ps_config))
ps_config_file.flush()
neon_local_config = stack.enter_context(tempfile.NamedTemporaryFile(mode="w+"))
neon_local_config.write(toml.dumps(config))
neon_local_config.flush()
cmd = [
"init",
f"--config={neon_local_config.name}",
"--pg-version",
self.env.pg_version,
f"--pageserver-config={ps_config_file.name}",
]
if force is not None: if force is not None:
cmd.extend(["--force", force]) cmd.extend(["--force", force])
storage = self.env.pageserver_remote_storage
append_pageserver_param_overrides(
params_to_update=cmd,
remote_storage=storage,
pageserver_config_override=self.env.pageserver_config_override,
)
s3_env_vars = None s3_env_vars = None
if isinstance(storage, S3Storage): if isinstance(remote_storage, S3Storage):
s3_env_vars = storage.access_env_vars() s3_env_vars = remote_storage.access_env_vars()
res = self.raw_cli(cmd, extra_env_vars=s3_env_vars) res = self.raw_cli(cmd, extra_env_vars=s3_env_vars)
res.check_returncode() res.check_returncode()
return res return res
def storage_controller_start(self): def storage_controller_start(self):
cmd = ["storage_controller", "start"] cmd = ["storage_controller", "start"]
@@ -1741,16 +1762,10 @@ class NeonCli(AbstractNeonCli):
def pageserver_start( def pageserver_start(
self, self,
id: int, id: int,
overrides: Tuple[str, ...] = (),
extra_env_vars: Optional[Dict[str, str]] = None, extra_env_vars: Optional[Dict[str, str]] = None,
) -> "subprocess.CompletedProcess[str]": ) -> "subprocess.CompletedProcess[str]":
start_args = ["pageserver", "start", f"--id={id}", *overrides] start_args = ["pageserver", "start", f"--id={id}"]
storage = self.env.pageserver_remote_storage storage = self.env.pageserver_remote_storage
append_pageserver_param_overrides(
params_to_update=start_args,
remote_storage=storage,
pageserver_config_override=self.env.pageserver_config_override,
)
if isinstance(storage, S3Storage): if isinstance(storage, S3Storage):
s3_env_vars = storage.access_env_vars() s3_env_vars = storage.access_env_vars()
@@ -2408,9 +2423,47 @@ class NeonPageserver(PgProtocol, LogUtils):
return self.workdir / "tenants" return self.workdir / "tenants"
return self.workdir / "tenants" / str(tenant_shard_id) return self.workdir / "tenants" / str(tenant_shard_id)
@property
def config_toml_path(self) -> Path:
return self.workdir / "pageserver.toml"
def edit_config_toml(self, edit_fn: Callable[[Dict[str, Any]], bool]):
"""
Edit the pageserver's config toml file in place.
The `edit_fn` is to manipulate the dict, and if it returns True, the file will be written.
If it returns False, no changes are made to the file system.
"""
path = self.config_toml_path
with open(path, "r") as f:
config = toml.load(f)
save = edit_fn(config)
if save:
with open(path, "w") as f:
toml.dump(config, f)
def patch_config_toml_nonrecursive(self, patch: Dict[str, Any]) -> Dict[str, Any]:
"""
Non-recursively merge the given `patch` dict into the existing config toml, using `dict.update()`.
Returns the replaced values.
If there was no previous value, the key is mapped to None.
This allows to restore the original value by calling this method with the returned dict.
"""
replacements = {}
def doit(config: Dict[str, Any]) -> bool:
while len(patch) > 0:
key, new = patch.popitem()
old = config.get(key, None)
config[key] = new
replacements[key] = old
return True
self.edit_config_toml(doit)
return replacements
def start( def start(
self, self,
overrides: Tuple[str, ...] = (),
extra_env_vars: Optional[Dict[str, str]] = None, extra_env_vars: Optional[Dict[str, str]] = None,
) -> "NeonPageserver": ) -> "NeonPageserver":
""" """
@@ -2420,9 +2473,7 @@ class NeonPageserver(PgProtocol, LogUtils):
""" """
assert self.running is False assert self.running is False
self.env.neon_cli.pageserver_start( self.env.neon_cli.pageserver_start(self.id, extra_env_vars=extra_env_vars)
self.id, overrides=overrides, extra_env_vars=extra_env_vars
)
self.running = True self.running = True
return self return self
@@ -2585,33 +2636,6 @@ class NeonPageserver(PgProtocol, LogUtils):
) )
def append_pageserver_param_overrides(
params_to_update: List[str],
remote_storage: Optional[RemoteStorage],
pageserver_config_override: Optional[str] = None,
):
if remote_storage is not None:
remote_storage_toml_table = remote_storage_to_toml_inline_table(remote_storage)
params_to_update.append(
f"--pageserver-config-override=remote_storage={remote_storage_toml_table}"
)
else:
params_to_update.append('--pageserver-config-override=remote_storage=""')
env_overrides = os.getenv("NEON_PAGESERVER_OVERRIDES")
if env_overrides is not None:
params_to_update += [
f"--pageserver-config-override={o.strip()}" for o in env_overrides.split(";")
]
if pageserver_config_override is not None:
params_to_update += [
f"--pageserver-config-override={o.strip()}"
for o in pageserver_config_override.split(";")
]
class PgBin: class PgBin:
"""A helper class for executing postgres binaries""" """A helper class for executing postgres binaries"""

View File

@@ -140,10 +140,13 @@ def test_branch_creation_many(neon_compare: NeonCompare, n_branches: int, shape:
# start without gc so we can time compaction with less noise; use shorter # start without gc so we can time compaction with less noise; use shorter
# period for compaction so it starts earlier # period for compaction so it starts earlier
def patch_default_tenant_config(config):
config["compaction_period"] = "3s"
config["gc_period"] = "0s"
return True
env.pageserver.edit_config_toml(patch_default_tenant_config)
env.pageserver.start( env.pageserver.start(
overrides=(
"--pageserver-config-override=tenant_config={ compaction_period = '3s', gc_period = '0s' }",
),
# this does print more than we want, but the number should be comparable between runs # this does print more than we want, but the number should be comparable between runs
extra_env_vars={ extra_env_vars={
"RUST_LOG": f"[compaction_loop{{tenant_id={env.initial_tenant}}}]=debug,info" "RUST_LOG": f"[compaction_loop{{tenant_id={env.initial_tenant}}}]=debug,info"

View File

@@ -5,7 +5,6 @@ from dataclasses import dataclass
from typing import Any, Dict, Iterable, Tuple from typing import Any, Dict, Iterable, Tuple
import pytest import pytest
import toml
from fixtures.log_helper import log from fixtures.log_helper import log
from fixtures.neon_fixtures import ( from fixtures.neon_fixtures import (
NeonEnv, NeonEnv,
@@ -45,17 +44,15 @@ def test_min_resident_size_override_handling(
ps_http.set_tenant_config(tenant_id, {}) ps_http.set_tenant_config(tenant_id, {})
assert_config(tenant_id, None, default_tenant_conf_value) assert_config(tenant_id, None, default_tenant_conf_value)
env.pageserver.stop()
if config_level_override is not None: if config_level_override is not None:
env.pageserver.start(
overrides=( def set_min_resident_size(config):
"--pageserver-config-override=tenant_config={ min_resident_size_override = " config["tenant_config"] = {"min_resident_size": config_level_override}
+ str(config_level_override) return True
+ " }",
) env.pageserver.edit_config_toml(set_min_resident_size)
) env.pageserver.stop()
else: env.pageserver.start()
env.pageserver.start()
tenant_id, _ = env.neon_cli.create_tenant() tenant_id, _ = env.neon_cli.create_tenant()
assert_overrides(tenant_id, config_level_override) assert_overrides(tenant_id, config_level_override)
@@ -164,34 +161,32 @@ class EvictionEnv:
usage eviction task is unknown; it might need to run one more iteration usage eviction task is unknown; it might need to run one more iteration
before assertions can be made. before assertions can be made.
""" """
disk_usage_config = {
"period": period,
"max_usage_pct": max_usage_pct,
"min_avail_bytes": min_avail_bytes,
"mock_statvfs": mock_behavior,
"eviction_order": eviction_order.config(),
}
enc = toml.TomlEncoder()
# these can sometimes happen during startup before any tenants have been # these can sometimes happen during startup before any tenants have been
# loaded, so nothing can be evicted, we just wait for next iteration which # loaded, so nothing can be evicted, we just wait for next iteration which
# is able to evict. # is able to evict.
pageserver.allowed_errors.append(".*WARN.* disk usage still high.*") pageserver.allowed_errors.append(".*WARN.* disk usage still high.*")
pageserver.start( pageserver.patch_config_toml_nonrecursive(
overrides=( {
"--pageserver-config-override=disk_usage_based_eviction=" "disk_usage_based_eviction": {
+ enc.dump_inline_table(disk_usage_config).replace("\n", " "), "period": period,
"max_usage_pct": max_usage_pct,
"min_avail_bytes": min_avail_bytes,
"mock_statvfs": mock_behavior,
"eviction_order": eviction_order.config(),
},
# Disk usage based eviction runs as a background task. # Disk usage based eviction runs as a background task.
# But pageserver startup delays launch of background tasks for some time, to prioritize initial logical size calculations during startup. # But pageserver startup delays launch of background tasks for some time, to prioritize initial logical size calculations during startup.
# But, initial logical size calculation may not be triggered if safekeepers don't publish new broker messages. # But, initial logical size calculation may not be triggered if safekeepers don't publish new broker messages.
# But, we only have a 10-second-timeout in this test. # But, we only have a 10-second-timeout in this test.
# So, disable the delay for this test. # So, disable the delay for this test.
"--pageserver-config-override=background_task_maximum_delay='0s'", "background_task_maximum_delay": "0s",
), }
) )
pageserver.start()
# we now do initial logical size calculation on startup, which on debug builds can fight with disk usage based eviction # we now do initial logical size calculation on startup, which on debug builds can fight with disk usage based eviction
for tenant_id, timeline_id in self.timelines: for tenant_id, timeline_id in self.timelines:
tenant_ps = self.neon_env.get_tenant_pageserver(tenant_id) tenant_ps = self.neon_env.get_tenant_pageserver(tenant_id)

View File

@@ -12,7 +12,6 @@ from fixtures.types import Lsn, TenantId, TimelineId
from fixtures.utils import wait_until from fixtures.utils import wait_until
# test that we cannot override node id after init
def test_pageserver_init_node_id( def test_pageserver_init_node_id(
neon_simple_env: NeonEnv, neon_binpath: Path, pg_distrib_dir: Path neon_simple_env: NeonEnv, neon_binpath: Path, pg_distrib_dir: Path
): ):
@@ -49,11 +48,7 @@ def test_pageserver_init_node_id(
bad_reinit = run_pageserver(good_init_cmd) bad_reinit = run_pageserver(good_init_cmd)
assert bad_reinit.returncode == 1, "pageserver refuses to init if already exists" assert bad_reinit.returncode == 1, "pageserver refuses to init if already exists"
assert "already exists, cannot init it" in bad_reinit.stderr assert "config file already exists" in bad_reinit.stderr
bad_update = run_pageserver(["--update-config", "-c", "id = 3"])
assert bad_update.returncode == 1, "pageserver should not allow updating node id"
assert "has node id already, it cannot be overridden" in bad_update.stderr
def check_client(env: NeonEnv, client: PageserverHttpClient): def check_client(env: NeonEnv, client: PageserverHttpClient):

View File

@@ -220,7 +220,12 @@ def test_generations_upgrade(neon_env_builder: NeonEnvBuilder):
# We will start a pageserver with no control_plane_api set, so it won't be able to self-register # We will start a pageserver with no control_plane_api set, so it won't be able to self-register
env.storage_controller.node_register(env.pageserver) env.storage_controller.node_register(env.pageserver)
env.pageserver.start(overrides=('--pageserver-config-override=control_plane_api=""',)) replaced_config = env.pageserver.patch_config_toml_nonrecursive(
{
"control_plane_api": "",
}
)
env.pageserver.start()
env.storage_controller.node_configure(env.pageserver.id, {"availability": "Active"}) env.storage_controller.node_configure(env.pageserver.id, {"availability": "Active"})
env.neon_cli.create_tenant( env.neon_cli.create_tenant(
@@ -251,8 +256,8 @@ def test_generations_upgrade(neon_env_builder: NeonEnvBuilder):
assert parse_generation_suffix(key) is None assert parse_generation_suffix(key) is None
env.pageserver.stop() env.pageserver.stop()
# Starting without the override that disabled control_plane_api # Starting without the override that disabled control_plane_api
env.pageserver.patch_config_toml_nonrecursive(replaced_config)
env.pageserver.start() env.pageserver.start()
generate_uploads_and_deletions(env, pageserver=env.pageserver, init=False) generate_uploads_and_deletions(env, pageserver=env.pageserver, init=False)
@@ -525,9 +530,12 @@ def test_emergency_mode(neon_env_builder: NeonEnvBuilder, pg_bin: PgBin):
# incident, but it might be unavoidable: if so, we want to be able to start up # incident, but it might be unavoidable: if so, we want to be able to start up
# and serve clients. # and serve clients.
env.pageserver.stop() # Non-immediate: implicitly checking that shutdown doesn't hang waiting for CP env.pageserver.stop() # Non-immediate: implicitly checking that shutdown doesn't hang waiting for CP
env.pageserver.start( replaced = env.pageserver.patch_config_toml_nonrecursive(
overrides=("--pageserver-config-override=control_plane_emergency_mode=true",), {
"control_plane_emergency_mode": True,
}
) )
env.pageserver.start()
# The pageserver should provide service to clients # The pageserver should provide service to clients
generate_uploads_and_deletions(env, init=False, pageserver=env.pageserver) generate_uploads_and_deletions(env, init=False, pageserver=env.pageserver)
@@ -549,6 +557,7 @@ def test_emergency_mode(neon_env_builder: NeonEnvBuilder, pg_bin: PgBin):
# The pageserver should work fine when subsequently restarted in non-emergency mode # The pageserver should work fine when subsequently restarted in non-emergency mode
env.pageserver.stop() # Non-immediate: implicitly checking that shutdown doesn't hang waiting for CP env.pageserver.stop() # Non-immediate: implicitly checking that shutdown doesn't hang waiting for CP
env.pageserver.patch_config_toml_nonrecursive(replaced)
env.pageserver.start() env.pageserver.start()
generate_uploads_and_deletions(env, init=False, pageserver=env.pageserver) generate_uploads_and_deletions(env, init=False, pageserver=env.pageserver)

View File

@@ -1,6 +1,3 @@
# It's possible to run any regular test with the local fs remote storage via
# env NEON_PAGESERVER_OVERRIDES="remote_storage={local_path='/tmp/neon_zzz/'}" poetry ......
import os import os
import queue import queue
import shutil import shutil

View File

@@ -290,9 +290,12 @@ def test_storage_controller_onboarding(neon_env_builder: NeonEnvBuilder, warm_up
# This is the pageserver where we'll initially create the tenant. Run it in emergency # This is the pageserver where we'll initially create the tenant. Run it in emergency
# mode so that it doesn't talk to storage controller, and do not register it. # mode so that it doesn't talk to storage controller, and do not register it.
env.pageservers[0].allowed_errors.append(".*Emergency mode!.*") env.pageservers[0].allowed_errors.append(".*Emergency mode!.*")
env.pageservers[0].start( env.pageservers[0].patch_config_toml_nonrecursive(
overrides=("--pageserver-config-override=control_plane_emergency_mode=true",), {
"control_plane_emergency_mode": True,
}
) )
env.pageservers[0].start()
origin_ps = env.pageservers[0] origin_ps = env.pageservers[0]
# These are the pageservers managed by the sharding service, where the tenant # These are the pageservers managed by the sharding service, where the tenant