Skip to content

Commit 2a15d6d

Browse files
authored
fix: three backportable bug fixes in pg_autoctl (#1127)
* fix: SIGHUP race in reload, multi_async report_lsn timing, drop-primary timeout Three CI fixes found by running pgaftest: 1. cli_common.c: add signal(SIGHUP, SIG_IGN) before kill() in cli_pg_autoctl_reload(). The daemon's supervisor_reload_services() broadcasts SIGHUP to all supervised children. A freshly exec'd one-shot command (e.g. 'pg_autoctl set node metadata') can receive that SIGHUP via PID reuse in the container's small PID namespace; since one-shot commands don't install a SIGHUP handler, the default disposition (terminate) kills the command before it returns, producing exit code 129 (128+SIGHUP). SIG_IGN is safe here because all callers are one-shot CLI commands. 2. multi_async.pgaf test_011: change 'wait until node4 assigned-state = report_lsn' to 'wait until node4 state is report_lsn'. The old check fires as soon as the monitor assigns the goal state, before node4 has stopped Postgres and reported its LSN. Disconnecting node4 at that point leaves the monitor waiting for a LSN report from a node that can no longer talk to it, blocking the entire promotion. 'state is' waits for the convergence event (reported == goal), so node4 has already reported its LSN when we disconnect it. 3. basic_operation.pgaf test_025: increase 'wait until node3 stopped' from 30s to 60s. Dropping the primary requires stopping Postgres (including WAL senders to the secondary), writing DROPPED twice to the monitor, and supervisor teardown — this regularly exceeds 30s in CI but fits comfortably within 60s. * fix: drop node succeeds when service already cleaned up state file The service_keeper.c changes on this branch delete the state file when the keeper exits after completing the DROP protocol (two confirmed node_active contacts in DROPPED state). This is correct: it prevents a restarted process from trying to re-register a dropped node. However, 'pg_autoctl drop node' (without --no-wait) also calls keeper_ensure_node_has_been_dropped() AFTER waiting for the running service to stop. That function reads the state file to get the node ID and then queries the monitor. If the service already exited cleanly and deleted the state file, the read fails and drop reports EXIT_CODE_MONITOR. Fix: after keeper_ensure_node_has_been_dropped() fails, check whether the state file is simply missing and we had a running service (pid != 0). The absence of the state file in that path is authoritative — the service only removes it after successfully completing the DROPPED protocol — so treat the drop as successful. * fix hba-lan: defer pgSetup init to after config file exists cli_common_pgsetup_init() calls ProbeConfigurationFileRole() which FAILs with FATAL when the keeper config file does not yet exist (node still initializing when setup block runs in parallel). Rewrite keeper_cli_pgsetup_hba_lan() to: - use keeperOptions.pgSetup.pgdata directly (no config file needed) - wait up to 60 s for pg_hba.conf to appear before writing rules - determine hostname from: keeper config → gethostname() → pghost - use &keeperOptions.pgSetup for pg_setup_wait_until_is_ready() - use keeperOptions.pgSetup.pg_ctl for pg_ctl_reload() gethostname() inside Docker returns the container service name (e.g. "node2") which resolves correctly on the Docker LAN network, covering the race window before the keeper config file is written. Add #include <unistd.h> for gethostname() on Linux. * style: fix citus_indent formatting in cli_do_misc.c * fix: add missing includes and functions for hba-lan cherry-pick The cherry-pick of the hba-lan fix introduced calls to three symbols that did not exist on main: - pghba_ensure_ident_map_entry (pghba.c/h): appends a missing entry to pg_ident.conf; used by cert auth to map the autoctl_node CN to the pgautofailover_replicator role. - pg_ctl_reload (pgctl.c/h): runs pg_ctl reload -D <pgdata>. - pghba.h, pgsetup.h, pgsql.h: missing includes in cli_do_misc.c. All implementations ported from pgaftest-infra:src/bin/pg_autoctl/ and src/bin/common/.
1 parent 15153cd commit 2a15d6d

7 files changed

Lines changed: 290 additions & 0 deletions

File tree

src/bin/pg_autoctl/cli_common.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,18 @@ cli_pg_autoctl_reload(const char *pidfile)
17441744
{
17451745
pid_t pid;
17461746

1747+
/*
1748+
* Ignore SIGHUP in this process before sending it to the daemon. The
1749+
* daemon's supervisor_reload_services() broadcasts SIGHUP to all its
1750+
* service children. A freshly exec'd one-shot command can share a PID
1751+
* with a recently-exited service (PID reuse), and since one-shot
1752+
* commands don't install a SIGHUP handler, the default disposition
1753+
* (terminate) would kill us before we even return. SIG_IGN is safe
1754+
* here because all callers are one-shot CLI commands that exit shortly
1755+
* after this call anyway.
1756+
*/
1757+
signal(SIGHUP, SIG_IGN);
1758+
17471759
if (read_pidfile(pidfile, &pid))
17481760
{
17491761
if (pid <= 0)

src/bin/pg_autoctl/cli_do_misc.c

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <getopt.h>
1313
#include <inttypes.h>
1414
#include <signal.h>
15+
#include <unistd.h>
1516

1617
#include "postgres_fe.h"
1718
#include "pqexpbuffer.h"
@@ -30,6 +31,9 @@
3031
#include "monitor.h"
3132
#include "monitor_config.h"
3233
#include "pgctl.h"
34+
#include "pghba.h"
35+
#include "pgsetup.h"
36+
#include "pgsql.h"
3337
#include "pgtuning.h"
3438
#include "primary_standby.h"
3539
#include "string_utils.h"
@@ -393,6 +397,169 @@ keeper_cli_pgsetup_tune(int argc, char **argv)
393397
}
394398

395399

400+
/*
401+
* keeper_cli_pgsetup_hba_lan appends LAN CIDR trust rules to pg_hba.conf
402+
* without connecting to Postgres (safe when TCP HBA hasn't been set up yet),
403+
* then reloads the configuration via pg_ctl.
404+
*
405+
* Intended for --skip-pg-hba setups where the operator needs to seed the
406+
* initial trust rules before pg_autoctl or replication can connect via TCP.
407+
*
408+
* Sequence:
409+
* 1. Wait up to 60 s for $PGDATA/pg_hba.conf to appear.
410+
* 2. Append "host all all <LAN-CIDR> trust" and
411+
* "host replication all <LAN-CIDR> trust" directly to the file.
412+
* 3. Wait for Postgres to be running (reads postmaster.pid, no TCP).
413+
* 4. pg_ctl reload -D $PGDATA.
414+
*/
415+
void
416+
keeper_cli_pgsetup_hba_lan(int argc, char **argv)
417+
{
418+
/*
419+
* Derive pgdata from command-line options directly, without calling
420+
* cli_common_pgsetup_init(). cli_common_pgsetup_init() calls
421+
* ProbeConfigurationFileRole() which fatals when the keeper config file
422+
* does not exist yet (e.g. node still initializing). We do not need the
423+
* full pgSetup here — only pgdata, hostname, auth method, and ssl.
424+
*/
425+
const char *pgdata = keeperOptions.pgSetup.pgdata;
426+
427+
if (IS_EMPTY_STRING_BUFFER(pgdata))
428+
{
429+
log_fatal("Please provide --pgdata or set PGDATA");
430+
exit(EXIT_CODE_BAD_ARGS);
431+
}
432+
433+
char hbaFile[MAXPGPATH];
434+
sformat(hbaFile, MAXPGPATH, "%s/pg_hba.conf", pgdata);
435+
436+
/* Step 1: wait for pg_hba.conf to appear (up to 60 s) */
437+
{
438+
int timeout = 60;
439+
time_t deadline = time(NULL) + timeout;
440+
while (!file_exists(hbaFile) && time(NULL) < deadline)
441+
{
442+
log_debug("Waiting for \"%s\" to appear", hbaFile);
443+
pg_usleep(500 * 1000);
444+
}
445+
if (!file_exists(hbaFile))
446+
{
447+
log_error("Timed out waiting for \"%s\" to appear", hbaFile);
448+
exit(EXIT_CODE_PGCTL);
449+
}
450+
}
451+
452+
/*
453+
* Step 2: determine the hostname for LAN CIDR lookups.
454+
*
455+
* Preference order:
456+
* a) keeper config file hostname (written early during pg_autoctl init)
457+
* b) OS hostname via gethostname() — inside a Docker container this is
458+
* the service name (e.g. "node2") which resolves correctly on the LAN
459+
* c) keeperOptions.pgSetup.pghost (last resort; may be a Unix socket
460+
* path like /var/run/postgresql that does not resolve as a hostname)
461+
*
462+
* The keeper config file is created before postgres initialises, so it is
463+
* normally available by the time pg_hba.conf appears. All three options
464+
* are tried so the command works even when called very early.
465+
*/
466+
char hostname[_POSIX_HOST_NAME_MAX] = "";
467+
468+
/* option a: keeper config */
469+
{
470+
KeeperConfig hbaConfig = keeperOptions;
471+
if (keeper_config_set_pathnames_from_pgdata(&hbaConfig.pathnames,
472+
pgdata) &&
473+
keeper_config_read_file(&hbaConfig,
474+
false /* missingPgdataIsOk */,
475+
true /* pgIsNotRunningIsOk */,
476+
true /* monitorDisabledIsOk */) &&
477+
!IS_EMPTY_STRING_BUFFER(hbaConfig.hostname))
478+
{
479+
strlcpy(hostname, hbaConfig.hostname, sizeof(hostname));
480+
}
481+
}
482+
483+
/* option b: OS hostname */
484+
if (IS_EMPTY_STRING_BUFFER(hostname))
485+
{
486+
if (gethostname(hostname, sizeof(hostname)) == 0)
487+
{
488+
log_debug("hba-lan: using OS hostname \"%s\"", hostname);
489+
}
490+
else
491+
{
492+
hostname[0] = '\0';
493+
}
494+
}
495+
496+
/* option c: pghost (may be a socket path — last resort) */
497+
if (IS_EMPTY_STRING_BUFFER(hostname))
498+
{
499+
strlcpy(hostname, keeperOptions.pgSetup.pghost, sizeof(hostname));
500+
}
501+
502+
/* --auth <method> defaults to "trust"; --ssl enables hostssl rules */
503+
const char *authMethod = keeperOptions.pgSetup.authMethod;
504+
if (IS_EMPTY_STRING_BUFFER(authMethod))
505+
{
506+
authMethod = "trust";
507+
}
508+
bool useSSL = keeperOptions.pgSetup.ssl.active;
509+
510+
/* cert auth for replication needs an ident map */
511+
bool isCert = (strcmp(authMethod, "cert") == 0);
512+
const char *replAuth = isCert ? "cert map=pgautofailover" : authMethod;
513+
514+
if (!pghba_enable_lan_cidr(NULL, useSSL,
515+
HBA_DATABASE_ALL, NULL,
516+
hostname, NULL, authMethod,
517+
HBA_EDIT_MINIMAL,
518+
pgdata))
519+
{
520+
log_error("Failed to add LAN CIDR HBA rule for \"all\" databases");
521+
exit(EXIT_CODE_PGCTL);
522+
}
523+
524+
if (!pghba_enable_lan_cidr(NULL, useSSL,
525+
HBA_DATABASE_REPLICATION, NULL,
526+
hostname, NULL, replAuth,
527+
HBA_EDIT_MINIMAL,
528+
pgdata))
529+
{
530+
log_error("Failed to add LAN CIDR HBA rule for replication");
531+
exit(EXIT_CODE_PGCTL);
532+
}
533+
534+
/* cert auth requires an ident map entry in pg_ident.conf */
535+
if (isCert)
536+
{
537+
if (!pghba_ensure_ident_map_entry(pgdata,
538+
"pgautofailover",
539+
PG_AUTOCTL_MONITOR_USERNAME,
540+
PG_AUTOCTL_REPLICA_USERNAME))
541+
{
542+
log_error("Failed to add cert ident map entry to pg_ident.conf");
543+
exit(EXIT_CODE_PGCTL);
544+
}
545+
}
546+
547+
/* Step 3: wait for Postgres to be running (PID file, no TCP needed) */
548+
if (!pg_setup_wait_until_is_ready(&keeperOptions.pgSetup, 60, LOG_INFO))
549+
{
550+
log_error("Postgres did not become ready within 60 s");
551+
exit(EXIT_CODE_PGCTL);
552+
}
553+
554+
/* Step 4: reload so the new HBA rules take effect */
555+
log_info("Reloading Postgres configuration in \"%s\"", pgdata);
556+
if (!pg_ctl_reload(keeperOptions.pgSetup.pg_ctl, pgdata))
557+
{
558+
exit(EXIT_CODE_PGCTL);
559+
}
560+
}
561+
562+
396563
/*
397564
* keeper_cli_init_standby initializes a standby
398565
*/

src/bin/pg_autoctl/cli_drop_node.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "service_monitor.h"
4040
#include "service_monitor_init.h"
4141
#include "signals.h"
42+
#include "file_utils.h"
4243
#include "string_utils.h"
4344

4445
/*
@@ -680,6 +681,22 @@ cli_drop_local_node(KeeperConfig *config, bool dropAndDestroy)
680681
config->formation,
681682
config->groupId);
682683
}
684+
else if (pid != 0 && !file_exists(config->pathnames.state))
685+
{
686+
/*
687+
* The running service stopped and deleted its own state file. The
688+
* service only removes the state file after completing the DROPPED
689+
* protocol (two confirmed monitor contacts in DROPPED state), so the
690+
* absence of the state file is authoritative: the drop succeeded.
691+
*/
692+
log_info("This node with id %lld in formation \"%s\" and group %d "
693+
"has been dropped from the monitor "
694+
"(confirmed by service clean-up)",
695+
(long long) keeperState->current_node_id,
696+
config->formation,
697+
config->groupId);
698+
dropped = true;
699+
}
683700
else
684701
{
685702
log_fatal("Failed to ensure that the local node with id %lld "

src/bin/pg_autoctl/pgctl.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,32 @@ pg_ctl_status(const char *pg_ctl, const char *pgdata, bool log_output)
20252025
}
20262026

20272027

2028+
/*
2029+
* pg_ctl_reload reloads PostgreSQL configuration by running "pg_ctl reload".
2030+
*/
2031+
bool
2032+
pg_ctl_reload(const char *pg_ctl, const char *pgdata)
2033+
{
2034+
Program program = run_program(pg_ctl, "-D", pgdata, "reload", NULL);
2035+
int returnCode = program.returnCode;
2036+
2037+
if (program.stdErr != NULL)
2038+
{
2039+
log_debug("%s", program.stdErr);
2040+
}
2041+
2042+
free_program(&program);
2043+
2044+
if (returnCode != 0)
2045+
{
2046+
log_error("pg_ctl reload -D %s failed (exit %d)", pgdata, returnCode);
2047+
return false;
2048+
}
2049+
2050+
return true;
2051+
}
2052+
2053+
20282054
/*
20292055
* pg_ctl_promote promotes a standby by running "pg_ctl promote"
20302056
*/

src/bin/pg_autoctl/pgctl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ bool pg_ctl_postgres(const char *pg_ctl, const char *pgdata, int pgport,
5757
bool pg_log_startup(const char *pgdata, int logLevel);
5858
bool pg_log_recovery_setup(const char *pgdata, int logLevel);
5959
bool pg_ctl_stop(const char *pg_ctl, const char *pgdata);
60+
bool pg_ctl_reload(const char *pg_ctl, const char *pgdata);
6061
int pg_ctl_status(const char *pg_ctl, const char *pgdata, bool log_output);
6162
bool pg_ctl_promote(const char *pg_ctl, const char *pgdata);
6263

src/bin/pg_autoctl/pghba.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,68 @@ pghba_enable_lan_cidr(PGSQL *pgsql,
700700
}
701701

702702

703+
/*
704+
* pghba_ensure_ident_map_entry ensures that pg_ident.conf contains the given
705+
* ident map entry, appending it if missing. Used with cert auth so that the
706+
* autoctl_node client certificate CN maps to the pgautofailover_replicator
707+
* PostgreSQL role for replication connections.
708+
*/
709+
bool
710+
pghba_ensure_ident_map_entry(const char *pgdata,
711+
const char *mapName,
712+
const char *systemUser,
713+
const char *pgUser)
714+
{
715+
char identFilePath[MAXPGPATH];
716+
char *contents = NULL;
717+
long size = 0L;
718+
719+
sformat(identFilePath, MAXPGPATH, "%s/pg_ident.conf", pgdata);
720+
721+
if (!read_file(identFilePath, &contents, &size))
722+
{
723+
log_error("Failed to read \"%s\"", identFilePath);
724+
return false;
725+
}
726+
727+
/* check whether this exact entry already exists */
728+
PQExpBuffer needle = createPQExpBuffer();
729+
if (!needle)
730+
{
731+
free(contents);
732+
log_error("Failed to allocate memory");
733+
return false;
734+
}
735+
appendPQExpBuffer(needle, "%s %s %s", mapName, systemUser, pgUser);
736+
737+
if (strstr(contents, needle->data) != NULL)
738+
{
739+
/* already present */
740+
destroyPQExpBuffer(needle);
741+
free(contents);
742+
return true;
743+
}
744+
destroyPQExpBuffer(needle);
745+
746+
/* append the new entry */
747+
FILE *f = fopen(identFilePath, "a"); /* IGNORE-BANNED */
748+
if (!f)
749+
{
750+
log_error("Failed to open \"%s\" for appending: %m", identFilePath);
751+
free(contents);
752+
return false;
753+
}
754+
fformat(f, "%s %s %s%s\n",
755+
mapName, systemUser, pgUser, HBA_LINE_COMMENT);
756+
fclose(f);
757+
free(contents);
758+
759+
log_info("Added ident map entry \"%s %s %s\" to \"%s\"",
760+
mapName, systemUser, pgUser, identFilePath);
761+
return true;
762+
}
763+
764+
703765
/*
704766
* hba_check_hostname returns true when the DNS setting looks compatible with
705767
* Postgres expectations for an HBA hostname entry.

src/bin/pg_autoctl/pghba.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ bool pghba_enable_lan_cidr(PGSQL *pgsql,
4949
HBAEditLevel hbaLevel,
5050
const char *pgdata);
5151

52+
bool pghba_ensure_ident_map_entry(const char *pgdata,
53+
const char *mapName,
54+
const char *systemUser,
55+
const char *pgUser);
56+
5257
bool pghba_check_hostname(const char *hostname, char *ipaddr, size_t size,
5358
bool *useHostname);
5459

0 commit comments

Comments
 (0)