Add neon.safekeeper_connstrings GUC

This GUC is very similar to neon.safekeepers, but instead of being
formatted as host:port, it is a Postgres connection string. The purpose
for changing how we connect to safekeepers is so that we can pass
various libpq SSL keyword parameters in the connection string.

A future PR will remove the `neon.safekeepers` GUC.

Signed-off-by: Tristan Partin <tristan@neon.tech>
This commit is contained in:
Tristan Partin
2025-05-05 11:00:14 -05:00
parent 0e0ad073bf
commit a3c5981106
22 changed files with 237 additions and 112 deletions

View File

@@ -49,9 +49,9 @@ def test_safekeepers_reconfigure_reorder(
old_sks = ""
with closing(endpoint.connect()) as conn:
with conn.cursor() as cur:
cur.execute("SHOW neon.safekeepers")
cur.execute("SHOW neon.safekeeper_connstrings")
res = cur.fetchone()
assert res is not None, "neon.safekeepers GUC is set"
assert res is not None, "neon.safekeeper_connstrings GUC is set"
old_sks = res[0]
# Reorder safekeepers
@@ -62,9 +62,9 @@ def test_safekeepers_reconfigure_reorder(
with closing(endpoint.connect()) as conn:
with conn.cursor() as cur:
cur.execute("SHOW neon.safekeepers")
cur.execute("SHOW neon.safekeeper_connstrings")
res = cur.fetchone()
assert res is not None, "neon.safekeepers GUC is set"
assert res is not None, "neon.safekeeper_connstrings GUC is set"
new_sks = res[0]
assert new_sks != old_sks, "GUC changes were applied"

View File

@@ -14,7 +14,7 @@ from contextlib import closing
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast
import psycopg2
import psycopg2.errors
@@ -685,7 +685,7 @@ class ProposerPostgres(PgProtocol):
f"neon.timeline_id = '{self.timeline_id}'\n",
f"neon.tenant_id = '{self.tenant_id}'\n",
"neon.pageserver_connstring = ''\n",
f"neon.safekeepers = '{safekeepers}'\n",
f"neon.safekeeper_connstrings = '{safekeepers}'\n",
f"listen_addresses = '{self.listen_addr}'\n",
f"port = '{self.port}'\n",
]
@@ -1464,7 +1464,15 @@ class SafekeeperEnv:
def get_safekeeper_connstrs(self):
assert self.safekeepers is not None, "safekeepers are not initialized"
return ",".join([sk_proc.args[2] for sk_proc in self.safekeepers])
def to_connstring(proc: subprocess.CompletedProcess[Any]) -> str:
"""
Parse <host>:<port> string into Postgres connection string
"""
(host, port, *_) = cast("str", proc.args[2]).split(":")
return f"host={host} port={port}"
return ",".join([to_connstring(sk_proc) for sk_proc in self.safekeepers])
def create_postgres(self):
assert self.tenant_id is not None, "tenant_id is not initialized"
@@ -2000,8 +2008,9 @@ def test_membership_api(neon_env_builder: NeonEnvBuilder):
def test_explicit_timeline_creation(neon_env_builder: NeonEnvBuilder):
"""
Test that having neon.safekeepers starting with g#n: with non zero n enables
generations, which as a side effect disables automatic timeline creation.
Test that having neon.safekeeper_connstrings starting with g#n: with non
zero n enables generations, which as a side effect disables automatic
timeline creation.
This is kind of bootstrapping test: here membership conf & timeline is
created manually, later storcon will do that.
@@ -2032,10 +2041,10 @@ def test_explicit_timeline_creation(neon_env_builder: NeonEnvBuilder):
def test_explicit_timeline_creation_storcon(neon_env_builder: NeonEnvBuilder):
"""
Test that having neon.safekeepers starting with g#n: with non zero n enables
generations, which as a side effect disables automatic timeline creation.
Like test_explicit_timeline_creation, but asks the storcon to
create membership conf & timeline.
Test that having neon.safekeeper_connstrings starting with g#n: with non
zero n enables generations, which as a side effect disables automatic
timeline creation. Like test_explicit_timeline_creation, but asks the
storcon to create membership conf & timeline.
"""
neon_env_builder.num_safekeepers = 3
neon_env_builder.storage_controller_config = {

View File

@@ -711,10 +711,10 @@ async def run_quorum_sanity(env: NeonEnv):
await quorum_sanity_single(env, [2, 3, 4], [1, 2, 3], [2, 3, 4], [2], False)
# Test various combinations of membership configurations / neon.safekeepers
# (list of safekeepers endpoint connects to) values / up & down safekeepers and
# check that endpont can start and write data when we have quorum and can't when
# we don't.
# Test various combinations of membership configurations /
# neon.safekeeper_connstrings (list of safekeeper connection strings) values /
# up & down safekeepers and check that endpont can start and write data when we
# have quorum and can't when we don't.
def test_quorum_sanity(neon_env_builder: NeonEnvBuilder):
neon_env_builder.num_safekeepers = 4
env = neon_env_builder.init_start()