mirror of
https://github.com/neondatabase/neon.git
synced 2025-12-26 15:49:58 +00:00
pageserver: use identity file as node id authority and remove init command and config-override flags (#7766)
Ansible will soon write the node id to `identity.toml` in the work dir for new pageservers. On the pageserver side, we read the node id from the identity file if it is present and use that as the source of truth. If the identity file is missing, cannot be read, or does not deserialise, start-up is aborted. This PR also removes the `--init` mode and the `--config-override` flag from the `pageserver` binary. The neon_local is already not using these flags anymore. Ansible still uses them until the linked change is merged & deployed, so, this PR has to land simultaneously or after the Ansible change due to that. Related Ansible change: https://github.com/neondatabase/aws/pull/1322 Cplane change to remove config-override usages: https://github.com/neondatabase/cloud/pull/13417 Closes: https://github.com/neondatabase/neon/issues/7736 Overall plan: https://www.notion.so/neondatabase/Rollout-Plan-simplified-pageserver-initialization-f935ae02b225444e8a41130b7d34e4ea?pvs=4 Co-authored-by: Christian Schwarz <christian@neon.tech>
This commit is contained in:
18
Dockerfile
18
Dockerfile
@@ -93,13 +93,14 @@ COPY --from=pg-build /home/nonroot/postgres_install.tar.gz /data/
|
|||||||
|
|
||||||
# By default, pageserver uses `.neon/` working directory in WORKDIR, so create one and fill it with the dummy config.
|
# By default, pageserver uses `.neon/` working directory in WORKDIR, so create one and fill it with the dummy config.
|
||||||
# Now, when `docker run ... pageserver` is run, it can start without errors, yet will have some default dummy values.
|
# Now, when `docker run ... pageserver` is run, it can start without errors, yet will have some default dummy values.
|
||||||
RUN mkdir -p /data/.neon/ && chown -R neon:neon /data/.neon/ \
|
RUN mkdir -p /data/.neon/ && \
|
||||||
&& /usr/local/bin/pageserver -D /data/.neon/ --init \
|
echo "id=1234" > "/data/.neon/identity.toml" && \
|
||||||
-c "id=1234" \
|
echo "broker_endpoint='http://storage_broker:50051'\n" \
|
||||||
-c "broker_endpoint='http://storage_broker:50051'" \
|
"pg_distrib_dir='/usr/local/'\n" \
|
||||||
-c "pg_distrib_dir='/usr/local/'" \
|
"listen_pg_addr='0.0.0.0:6400'\n" \
|
||||||
-c "listen_pg_addr='0.0.0.0:6400'" \
|
"listen_http_addr='0.0.0.0:9898'\n" \
|
||||||
-c "listen_http_addr='0.0.0.0:9898'"
|
> /data/.neon/pageserver.toml && \
|
||||||
|
chown -R neon:neon /data/.neon
|
||||||
|
|
||||||
# When running a binary that links with libpq, default to using our most recent postgres version. Binaries
|
# When running a binary that links with libpq, default to using our most recent postgres version. Binaries
|
||||||
# that want a particular postgres version will select it explicitly: this is just a default.
|
# that want a particular postgres version will select it explicitly: this is just a default.
|
||||||
@@ -110,3 +111,6 @@ VOLUME ["/data"]
|
|||||||
USER neon
|
USER neon
|
||||||
EXPOSE 6400
|
EXPOSE 6400
|
||||||
EXPOSE 9898
|
EXPOSE 9898
|
||||||
|
|
||||||
|
CMD /usr/local/bin/pageserver -D /data/.neon
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use pageserver_client::mgmt_api;
|
|||||||
use postgres_backend::AuthType;
|
use postgres_backend::AuthType;
|
||||||
use postgres_connection::{parse_host_port, PgConnectionConfig};
|
use postgres_connection::{parse_host_port, PgConnectionConfig};
|
||||||
use utils::auth::{Claims, Scope};
|
use utils::auth::{Claims, Scope};
|
||||||
|
use utils::id::NodeId;
|
||||||
use utils::{
|
use utils::{
|
||||||
id::{TenantId, TimelineId},
|
id::{TenantId, TimelineId},
|
||||||
lsn::Lsn,
|
lsn::Lsn,
|
||||||
@@ -74,6 +75,10 @@ impl PageServerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pageserver_make_identity_toml(&self, node_id: NodeId) -> toml_edit::Document {
|
||||||
|
toml_edit::Document::from_str(&format!("id={node_id}")).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn pageserver_init_make_toml(
|
fn pageserver_init_make_toml(
|
||||||
&self,
|
&self,
|
||||||
conf: NeonLocalInitPageserverConf,
|
conf: NeonLocalInitPageserverConf,
|
||||||
@@ -186,6 +191,19 @@ impl PageServerNode {
|
|||||||
.write_all(config.to_string().as_bytes())
|
.write_all(config.to_string().as_bytes())
|
||||||
.context("write pageserver toml")?;
|
.context("write pageserver toml")?;
|
||||||
drop(config_file);
|
drop(config_file);
|
||||||
|
|
||||||
|
let identity_file_path = datadir.join("identity.toml");
|
||||||
|
let mut identity_file = std::fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(identity_file_path)
|
||||||
|
.with_context(|| format!("open identity toml for write: {config_file_path:?}"))?;
|
||||||
|
let identity_toml = self.pageserver_make_identity_toml(node_id);
|
||||||
|
identity_file
|
||||||
|
.write_all(identity_toml.to_string().as_bytes())
|
||||||
|
.context("write identity toml")?;
|
||||||
|
drop(identity_toml);
|
||||||
|
|
||||||
// TODO: invoke a TBD config-check command to validate that pageserver will start with the written config
|
// TODO: invoke a TBD config-check command to validate that pageserver will start with the written config
|
||||||
|
|
||||||
// Write metadata file, used by pageserver on startup to register itself with
|
// Write metadata file, used by pageserver on startup to register itself with
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ echo $result | jq .
|
|||||||
|
|
||||||
generate_id timeline_id
|
generate_id timeline_id
|
||||||
PARAMS=(
|
PARAMS=(
|
||||||
-sb
|
-sbf
|
||||||
-X POST
|
-X POST
|
||||||
-H "Content-Type: application/json"
|
-H "Content-Type: application/json"
|
||||||
-d "{\"new_timeline_id\": \"${timeline_id}\", \"pg_version\": ${PG_VERSION}}"
|
-d "{\"new_timeline_id\": \"${timeline_id}\", \"pg_version\": ${PG_VERSION}}"
|
||||||
|
|||||||
@@ -31,25 +31,14 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
image: ${REPOSITORY:-neondatabase}/neon:${TAG:-latest}
|
image: ${REPOSITORY:-neondatabase}/neon:${TAG:-latest}
|
||||||
environment:
|
environment:
|
||||||
- BROKER_ENDPOINT='http://storage_broker:50051'
|
|
||||||
- AWS_ACCESS_KEY_ID=minio
|
- AWS_ACCESS_KEY_ID=minio
|
||||||
- AWS_SECRET_ACCESS_KEY=password
|
- AWS_SECRET_ACCESS_KEY=password
|
||||||
#- RUST_BACKTRACE=1
|
#- RUST_BACKTRACE=1
|
||||||
ports:
|
ports:
|
||||||
#- 6400:6400 # pg protocol handler
|
#- 6400:6400 # pg protocol handler
|
||||||
- 9898:9898 # http endpoints
|
- 9898:9898 # http endpoints
|
||||||
entrypoint:
|
volumes:
|
||||||
- "/bin/sh"
|
- ./pageserver_config:/data/.neon/
|
||||||
- "-c"
|
|
||||||
command:
|
|
||||||
- "/usr/local/bin/pageserver -D /data/.neon/
|
|
||||||
-c \"broker_endpoint=$$BROKER_ENDPOINT\"
|
|
||||||
-c \"listen_pg_addr='0.0.0.0:6400'\"
|
|
||||||
-c \"listen_http_addr='0.0.0.0:9898'\"
|
|
||||||
-c \"remote_storage={endpoint='http://minio:9000',
|
|
||||||
bucket_name='neon',
|
|
||||||
bucket_region='eu-north-1',
|
|
||||||
prefix_in_bucket='/pageserver/'}\""
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- storage_broker
|
- storage_broker
|
||||||
- minio_create_buckets
|
- minio_create_buckets
|
||||||
|
|||||||
1
docker-compose/pageserver_config/identity.toml
Normal file
1
docker-compose/pageserver_config/identity.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
id=1234
|
||||||
5
docker-compose/pageserver_config/pageserver.toml
Normal file
5
docker-compose/pageserver_config/pageserver.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
broker_endpoint='http://storage_broker:50051'
|
||||||
|
pg_distrib_dir='/usr/local/'
|
||||||
|
listen_pg_addr='0.0.0.0:6400'
|
||||||
|
listen_http_addr='0.0.0.0:9898'
|
||||||
|
remote_storage={ endpoint='http://minio:9000', bucket_name='neon', bucket_region='eu-north-1', prefix_in_bucket='/pageserver' }
|
||||||
@@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
//! Main entry point for the Page Server executable.
|
//! Main entry point for the Page Server executable.
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::env::{var, VarError};
|
use std::env::{var, VarError};
|
||||||
use std::io::Read;
|
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 anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use camino::Utf8Path;
|
use camino::Utf8Path;
|
||||||
use clap::{Arg, ArgAction, Command};
|
use clap::{Arg, ArgAction, Command};
|
||||||
|
|
||||||
use metrics::launch_timestamp::{set_launch_timestamp_metric, LaunchTimestamp};
|
use metrics::launch_timestamp::{set_launch_timestamp_metric, LaunchTimestamp};
|
||||||
|
use pageserver::config::PageserverIdentity;
|
||||||
use pageserver::control_plane_client::ControlPlaneClient;
|
use pageserver::control_plane_client::ControlPlaneClient;
|
||||||
use pageserver::disk_usage_eviction_task::{self, launch_disk_usage_global_eviction_task};
|
use pageserver::disk_usage_eviction_task::{self, launch_disk_usage_global_eviction_task};
|
||||||
use pageserver::metrics::{STARTUP_DURATION, STARTUP_IS_LOADING};
|
use pageserver::metrics::{STARTUP_DURATION, STARTUP_IS_LOADING};
|
||||||
@@ -29,7 +30,7 @@ use tracing::*;
|
|||||||
|
|
||||||
use metrics::set_build_info_metric;
|
use metrics::set_build_info_metric;
|
||||||
use pageserver::{
|
use pageserver::{
|
||||||
config::{defaults::*, PageServerConf},
|
config::PageServerConf,
|
||||||
context::{DownloadBehavior, RequestContext},
|
context::{DownloadBehavior, RequestContext},
|
||||||
deletion_queue::DeletionQueue,
|
deletion_queue::DeletionQueue,
|
||||||
http, page_cache, page_service, task_mgr,
|
http, page_cache, page_service, task_mgr,
|
||||||
@@ -88,18 +89,13 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.with_context(|| format!("Error opening workdir '{workdir}'"))?;
|
.with_context(|| format!("Error opening workdir '{workdir}'"))?;
|
||||||
|
|
||||||
let cfg_file_path = workdir.join("pageserver.toml");
|
let cfg_file_path = workdir.join("pageserver.toml");
|
||||||
|
let identity_file_path = workdir.join("identity.toml");
|
||||||
|
|
||||||
// Set CWD to workdir for non-daemon modes
|
// Set CWD to workdir for non-daemon modes
|
||||||
env::set_current_dir(&workdir)
|
env::set_current_dir(&workdir)
|
||||||
.with_context(|| format!("Failed to set application's current dir to '{workdir}'"))?;
|
.with_context(|| format!("Failed to set application's current dir to '{workdir}'"))?;
|
||||||
|
|
||||||
let conf = match initialize_config(&cfg_file_path, arg_matches, &workdir)? {
|
let conf = initialize_config(&identity_file_path, &cfg_file_path, &workdir)?;
|
||||||
ControlFlow::Continue(conf) => conf,
|
|
||||||
ControlFlow::Break(()) => {
|
|
||||||
info!("Pageserver config init successful");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize logging.
|
// Initialize logging.
|
||||||
//
|
//
|
||||||
@@ -154,70 +150,55 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_config(
|
fn initialize_config(
|
||||||
|
identity_file_path: &Utf8Path,
|
||||||
cfg_file_path: &Utf8Path,
|
cfg_file_path: &Utf8Path,
|
||||||
arg_matches: clap::ArgMatches,
|
|
||||||
workdir: &Utf8Path,
|
workdir: &Utf8Path,
|
||||||
) -> anyhow::Result<ControlFlow<(), &'static PageServerConf>> {
|
) -> anyhow::Result<&'static PageServerConf> {
|
||||||
let init = arg_matches.get_flag("init");
|
// The deployment orchestrator writes out an indentity file containing the node id
|
||||||
|
// for all pageservers. This file is the source of truth for the node id. In order
|
||||||
let file_contents: Option<toml_edit::Document> = match std::fs::File::open(cfg_file_path) {
|
// to allow for rolling back pageserver releases, the node id is also included in
|
||||||
|
// the pageserver config that the deployment orchestrator writes to disk for the pageserver.
|
||||||
|
// A rolled back version of the pageserver will get the node id from the pageserver.toml
|
||||||
|
// config file.
|
||||||
|
let identity = match std::fs::File::open(identity_file_path) {
|
||||||
Ok(mut f) => {
|
Ok(mut f) => {
|
||||||
if init {
|
let md = f.metadata().context("stat config file")?;
|
||||||
anyhow::bail!("config file already exists: {cfg_file_path}");
|
if !md.is_file() {
|
||||||
|
anyhow::bail!("Pageserver found identity file but it is a dir entry: {identity_file_path}. Aborting start up ...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut s = String::new();
|
||||||
|
f.read_to_string(&mut s).context("read identity file")?;
|
||||||
|
toml_edit::de::from_str::<PageserverIdentity>(&s)?
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
anyhow::bail!("Pageserver could not read identity file: {identity_file_path}: {e}. Aborting start up ...");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config: toml_edit::Document = match std::fs::File::open(cfg_file_path) {
|
||||||
|
Ok(mut f) => {
|
||||||
let md = f.metadata().context("stat config file")?;
|
let md = f.metadata().context("stat config file")?;
|
||||||
if md.is_file() {
|
if md.is_file() {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
f.read_to_string(&mut s).context("read config file")?;
|
f.read_to_string(&mut s).context("read config file")?;
|
||||||
Some(s.parse().context("parse config file toml")?)
|
s.parse().context("parse config file toml")?
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("directory entry exists but is not a file: {cfg_file_path}");
|
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) => {
|
Err(e) => {
|
||||||
anyhow::bail!("open pageserver config: {e}: {cfg_file_path}");
|
anyhow::bail!("open pageserver config: {e}: {cfg_file_path}");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut effective_config = file_contents.unwrap_or_else(|| {
|
debug!("Using pageserver toml: {config}");
|
||||||
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") {
|
|
||||||
for option_line in values {
|
|
||||||
let doc = toml_edit::Document::from_str(option_line).with_context(|| {
|
|
||||||
format!("Option '{option_line}' could not be parsed as a toml document")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for (key, item) in doc.iter() {
|
|
||||||
effective_config.insert(key, item.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Resulting toml: {effective_config}");
|
|
||||||
|
|
||||||
// Construct the runtime representation
|
// Construct the runtime representation
|
||||||
let conf = PageServerConf::parse_and_validate(&effective_config, workdir)
|
let conf = PageServerConf::parse_and_validate(identity.id, &config, workdir)
|
||||||
.context("Failed to parse pageserver configuration")?;
|
.context("Failed to parse pageserver configuration")?;
|
||||||
|
|
||||||
if init {
|
Ok(Box::leak(Box::new(conf)))
|
||||||
info!("Writing pageserver config to '{cfg_file_path}'");
|
|
||||||
|
|
||||||
std::fs::write(cfg_file_path, effective_config.to_string())
|
|
||||||
.with_context(|| format!("Failed to write pageserver config to '{cfg_file_path}'"))?;
|
|
||||||
info!("Config successfully written to '{cfg_file_path}'")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(if init {
|
|
||||||
ControlFlow::Break(())
|
|
||||||
} else {
|
|
||||||
ControlFlow::Continue(Box::leak(Box::new(conf)))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WaitForPhaseResult<F: std::future::Future + Unpin> {
|
struct WaitForPhaseResult<F: std::future::Future + Unpin> {
|
||||||
@@ -710,28 +691,12 @@ fn cli() -> Command {
|
|||||||
Command::new("Neon page server")
|
Command::new("Neon page server")
|
||||||
.about("Materializes WAL stream to pages and serves them to the postgres")
|
.about("Materializes WAL stream to pages and serves them to the postgres")
|
||||||
.version(version())
|
.version(version())
|
||||||
.arg(
|
|
||||||
Arg::new("init")
|
|
||||||
.long("init")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Initialize pageserver with all given config overrides"),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("workdir")
|
Arg::new("workdir")
|
||||||
.short('D')
|
.short('D')
|
||||||
.long("workdir")
|
.long("workdir")
|
||||||
.help("Working directory for the pageserver"),
|
.help("Working directory for the pageserver"),
|
||||||
)
|
)
|
||||||
// See `settings.md` for more details on the extra configuration patameters pageserver can process
|
|
||||||
.arg(
|
|
||||||
Arg::new("config-override")
|
|
||||||
.long("config-override")
|
|
||||||
.short('c')
|
|
||||||
.num_args(1)
|
|
||||||
.action(ArgAction::Append)
|
|
||||||
.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}\"`"),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("enabled-features")
|
Arg::new("enabled-features")
|
||||||
.long("enabled-features")
|
.long("enabled-features")
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
use pageserver_api::{models::ImageCompressionAlgorithm, shard::TenantShardId};
|
use pageserver_api::{models::ImageCompressionAlgorithm, shard::TenantShardId};
|
||||||
use remote_storage::{RemotePath, RemoteStorageConfig};
|
use remote_storage::{RemotePath, RemoteStorageConfig};
|
||||||
use serde;
|
|
||||||
use serde::de::IntoDeserializer;
|
use serde::de::IntoDeserializer;
|
||||||
|
use serde::{self, Deserialize};
|
||||||
use std::env;
|
use std::env;
|
||||||
use storage_broker::Uri;
|
use storage_broker::Uri;
|
||||||
use utils::crashsafe::path_with_suffix_extension;
|
use utils::crashsafe::path_with_suffix_extension;
|
||||||
@@ -406,6 +406,13 @@ struct PageServerConfigBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PageServerConfigBuilder {
|
impl PageServerConfigBuilder {
|
||||||
|
fn new(node_id: NodeId) -> Self {
|
||||||
|
let mut this = Self::default();
|
||||||
|
this.id(node_id);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_values() -> Self {
|
fn default_values() -> Self {
|
||||||
use self::BuilderValue::*;
|
use self::BuilderValue::*;
|
||||||
@@ -881,8 +888,12 @@ impl PageServerConf {
|
|||||||
/// validating the input and failing on errors.
|
/// validating the input and failing on errors.
|
||||||
///
|
///
|
||||||
/// This leaves any options not present in the file in the built-in defaults.
|
/// This leaves any options not present in the file in the built-in defaults.
|
||||||
pub fn parse_and_validate(toml: &Document, workdir: &Utf8Path) -> anyhow::Result<Self> {
|
pub fn parse_and_validate(
|
||||||
let mut builder = PageServerConfigBuilder::default();
|
node_id: NodeId,
|
||||||
|
toml: &Document,
|
||||||
|
workdir: &Utf8Path,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let mut builder = PageServerConfigBuilder::new(node_id);
|
||||||
builder.workdir(workdir.to_owned());
|
builder.workdir(workdir.to_owned());
|
||||||
|
|
||||||
let mut t_conf = TenantConfOpt::default();
|
let mut t_conf = TenantConfOpt::default();
|
||||||
@@ -913,7 +924,8 @@ impl PageServerConf {
|
|||||||
"tenant_config" => {
|
"tenant_config" => {
|
||||||
t_conf = TenantConfOpt::try_from(item.to_owned()).context(format!("failed to parse: '{key}'"))?;
|
t_conf = TenantConfOpt::try_from(item.to_owned()).context(format!("failed to parse: '{key}'"))?;
|
||||||
}
|
}
|
||||||
"id" => builder.id(NodeId(parse_toml_u64(key, item)?)),
|
"id" => {}, // Ignoring `id` field in pageserver.toml - using identity.toml as the source of truth
|
||||||
|
// Logging is not set up yet, so we can't do it.
|
||||||
"broker_endpoint" => builder.broker_endpoint(parse_toml_string(key, item)?.parse().context("failed to parse broker endpoint")?),
|
"broker_endpoint" => builder.broker_endpoint(parse_toml_string(key, item)?.parse().context("failed to parse broker endpoint")?),
|
||||||
"broker_keepalive_interval" => builder.broker_keepalive_interval(parse_toml_duration(key, item)?),
|
"broker_keepalive_interval" => builder.broker_keepalive_interval(parse_toml_duration(key, item)?),
|
||||||
"log_format" => builder.log_format(
|
"log_format" => builder.log_format(
|
||||||
@@ -1090,6 +1102,12 @@ impl PageServerConf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct PageserverIdentity {
|
||||||
|
pub id: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
// Helper functions to parse a toml Item
|
// Helper functions to parse a toml Item
|
||||||
|
|
||||||
fn parse_toml_string(name: &str, item: &Item) -> Result<String> {
|
fn parse_toml_string(name: &str, item: &Item) -> Result<String> {
|
||||||
@@ -1259,7 +1277,7 @@ background_task_maximum_delay = '334 s'
|
|||||||
);
|
);
|
||||||
let toml = config_string.parse()?;
|
let toml = config_string.parse()?;
|
||||||
|
|
||||||
let parsed_config = PageServerConf::parse_and_validate(&toml, &workdir)
|
let parsed_config = PageServerConf::parse_and_validate(NodeId(10), &toml, &workdir)
|
||||||
.unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
|
.unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1341,7 +1359,7 @@ background_task_maximum_delay = '334 s'
|
|||||||
);
|
);
|
||||||
let toml = config_string.parse()?;
|
let toml = config_string.parse()?;
|
||||||
|
|
||||||
let parsed_config = PageServerConf::parse_and_validate(&toml, &workdir)
|
let parsed_config = PageServerConf::parse_and_validate(NodeId(10), &toml, &workdir)
|
||||||
.unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
|
.unwrap_or_else(|e| panic!("Failed to parse config '{config_string}', reason: {e:?}"));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1431,12 +1449,13 @@ broker_endpoint = '{broker_endpoint}'
|
|||||||
|
|
||||||
let toml = config_string.parse()?;
|
let toml = config_string.parse()?;
|
||||||
|
|
||||||
let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir)
|
let parsed_remote_storage_config =
|
||||||
.unwrap_or_else(|e| {
|
PageServerConf::parse_and_validate(NodeId(10), &toml, &workdir)
|
||||||
panic!("Failed to parse config '{config_string}', reason: {e:?}")
|
.unwrap_or_else(|e| {
|
||||||
})
|
panic!("Failed to parse config '{config_string}', reason: {e:?}")
|
||||||
.remote_storage_config
|
})
|
||||||
.expect("Should have remote storage config for the local FS");
|
.remote_storage_config
|
||||||
|
.expect("Should have remote storage config for the local FS");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed_remote_storage_config,
|
parsed_remote_storage_config,
|
||||||
@@ -1492,12 +1511,13 @@ broker_endpoint = '{broker_endpoint}'
|
|||||||
|
|
||||||
let toml = config_string.parse()?;
|
let toml = config_string.parse()?;
|
||||||
|
|
||||||
let parsed_remote_storage_config = PageServerConf::parse_and_validate(&toml, &workdir)
|
let parsed_remote_storage_config =
|
||||||
.unwrap_or_else(|e| {
|
PageServerConf::parse_and_validate(NodeId(10), &toml, &workdir)
|
||||||
panic!("Failed to parse config '{config_string}', reason: {e:?}")
|
.unwrap_or_else(|e| {
|
||||||
})
|
panic!("Failed to parse config '{config_string}', reason: {e:?}")
|
||||||
.remote_storage_config
|
})
|
||||||
.expect("Should have remote storage config for S3");
|
.remote_storage_config
|
||||||
|
.expect("Should have remote storage config for S3");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed_remote_storage_config,
|
parsed_remote_storage_config,
|
||||||
@@ -1576,7 +1596,7 @@ threshold = "20m"
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let toml: Document = pageserver_conf_toml.parse()?;
|
let toml: Document = pageserver_conf_toml.parse()?;
|
||||||
let conf = PageServerConf::parse_and_validate(&toml, &workdir)?;
|
let conf = PageServerConf::parse_and_validate(NodeId(333), &toml, &workdir)?;
|
||||||
|
|
||||||
assert_eq!(conf.pg_distrib_dir, pg_distrib_dir);
|
assert_eq!(conf.pg_distrib_dir, pg_distrib_dir);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1592,7 +1612,11 @@ threshold = "20m"
|
|||||||
.evictions_low_residence_duration_metric_threshold,
|
.evictions_low_residence_duration_metric_threshold,
|
||||||
Duration::from_secs(20 * 60)
|
Duration::from_secs(20 * 60)
|
||||||
);
|
);
|
||||||
assert_eq!(conf.id, NodeId(222));
|
|
||||||
|
// Assert that the node id provided by the indentity file (threaded
|
||||||
|
// through the call to [`PageServerConf::parse_and_validate`] is
|
||||||
|
// used.
|
||||||
|
assert_eq!(conf.id, NodeId(333));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
conf.disk_usage_based_eviction,
|
conf.disk_usage_based_eviction,
|
||||||
Some(DiskUsageEvictionTaskConfig {
|
Some(DiskUsageEvictionTaskConfig {
|
||||||
@@ -1637,7 +1661,7 @@ threshold = "20m"
|
|||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let toml: Document = pageserver_conf_toml.parse().unwrap();
|
let toml: Document = pageserver_conf_toml.parse().unwrap();
|
||||||
let conf = PageServerConf::parse_and_validate(&toml, &workdir).unwrap();
|
let conf = PageServerConf::parse_and_validate(NodeId(222), &toml, &workdir).unwrap();
|
||||||
|
|
||||||
match &conf.default_tenant_conf.eviction_policy {
|
match &conf.default_tenant_conf.eviction_policy {
|
||||||
EvictionPolicy::OnlyImitiate(t) => {
|
EvictionPolicy::OnlyImitiate(t) => {
|
||||||
@@ -1656,7 +1680,7 @@ threshold = "20m"
|
|||||||
remote_storage = {}
|
remote_storage = {}
|
||||||
"#;
|
"#;
|
||||||
let doc = toml_edit::Document::from_str(input).unwrap();
|
let doc = toml_edit::Document::from_str(input).unwrap();
|
||||||
let err = PageServerConf::parse_and_validate(&doc, &workdir)
|
let err = PageServerConf::parse_and_validate(NodeId(222), &doc, &workdir)
|
||||||
.expect_err("empty remote_storage field should fail, don't specify it if you want no remote_storage");
|
.expect_err("empty remote_storage field should fail, don't specify it if you want no remote_storage");
|
||||||
assert!(format!("{err}").contains("remote_storage"), "{err}");
|
assert!(format!("{err}").contains("remote_storage"), "{err}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import toml
|
|
||||||
from fixtures.common_types import Lsn, TenantId, TimelineId
|
from fixtures.common_types import Lsn, TenantId, TimelineId
|
||||||
from fixtures.neon_fixtures import (
|
from fixtures.neon_fixtures import (
|
||||||
DEFAULT_BRANCH_NAME,
|
DEFAULT_BRANCH_NAME,
|
||||||
@@ -13,67 +10,6 @@ from fixtures.pageserver.http import PageserverHttpClient
|
|||||||
from fixtures.utils import wait_until
|
from fixtures.utils import wait_until
|
||||||
|
|
||||||
|
|
||||||
def test_pageserver_init_node_id(neon_simple_env: NeonEnv, neon_binpath: Path):
|
|
||||||
"""
|
|
||||||
NB: The neon_local doesn't use `--init` mode anymore, but our production
|
|
||||||
deployment still does => https://github.com/neondatabase/aws/pull/1322
|
|
||||||
"""
|
|
||||||
workdir = neon_simple_env.pageserver.workdir
|
|
||||||
pageserver_config = workdir / "pageserver.toml"
|
|
||||||
pageserver_bin = neon_binpath / "pageserver"
|
|
||||||
|
|
||||||
def run_pageserver(args):
|
|
||||||
return subprocess.run(
|
|
||||||
[str(pageserver_bin), "-D", str(workdir), *args],
|
|
||||||
check=False,
|
|
||||||
universal_newlines=True,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
|
|
||||||
neon_simple_env.pageserver.stop()
|
|
||||||
|
|
||||||
with open(neon_simple_env.pageserver.config_toml_path, "r") as f:
|
|
||||||
ps_config = toml.load(f)
|
|
||||||
|
|
||||||
required_config_keys = [
|
|
||||||
"pg_distrib_dir",
|
|
||||||
"listen_pg_addr",
|
|
||||||
"listen_http_addr",
|
|
||||||
"pg_auth_type",
|
|
||||||
"http_auth_type",
|
|
||||||
# TODO: only needed for NEON_PAGESERVER_PANIC_ON_UNSPECIFIED_COMPACTION_ALGORITHM in https://github.com/neondatabase/neon/pull/7748
|
|
||||||
# "tenant_config",
|
|
||||||
]
|
|
||||||
required_config_overrides = [
|
|
||||||
f"--config-override={toml.dumps({k: ps_config[k]})}" for k in required_config_keys
|
|
||||||
]
|
|
||||||
|
|
||||||
pageserver_config.unlink()
|
|
||||||
|
|
||||||
bad_init = run_pageserver(["--init", *required_config_overrides])
|
|
||||||
assert (
|
|
||||||
bad_init.returncode == 1
|
|
||||||
), "pageserver should not be able to init new config without the node id"
|
|
||||||
assert 'missing config value "id"' in bad_init.stderr
|
|
||||||
assert not pageserver_config.exists(), "config file should not be created after init error"
|
|
||||||
|
|
||||||
good_init_cmd = [
|
|
||||||
"--init",
|
|
||||||
f"--config-override=id={ps_config['id']}",
|
|
||||||
*required_config_overrides,
|
|
||||||
]
|
|
||||||
completed_init = run_pageserver(good_init_cmd)
|
|
||||||
assert (
|
|
||||||
completed_init.returncode == 0
|
|
||||||
), "pageserver should be able to create a new config with the node id given"
|
|
||||||
assert pageserver_config.exists(), "config file should be created successfully"
|
|
||||||
|
|
||||||
bad_reinit = run_pageserver(good_init_cmd)
|
|
||||||
assert bad_reinit.returncode == 1, "pageserver refuses to init if already exists"
|
|
||||||
assert "config file already exists" in bad_reinit.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def check_client(env: NeonEnv, client: PageserverHttpClient):
|
def check_client(env: NeonEnv, client: PageserverHttpClient):
|
||||||
pg_version = env.pg_version
|
pg_version = env.pg_version
|
||||||
initial_tenant = env.initial_tenant
|
initial_tenant = env.initial_tenant
|
||||||
|
|||||||
Reference in New Issue
Block a user