Skip to content

Commit 5ef1078

Browse files
authored
Feature/zero config pg autoctl (#576)
* Add --monitor option to pg_autoctl create|drop formation. This allows creating a formation from a node that's not been created yet. * Add --monitor to pg_autoctl show uri|state|events. * Implement --monitor in more commands. pg_autoctl get|set formation settings pg_autoctl get|set formation number-sync-standbys pg_autoctl get|set node replication-quorum pg_autoctl get|set node candidate-priority pg_autoctl show uri pg_autoctl show events pg_autoctl show state pg_autoctl show settings pg_autoctl show standby-names pg_autoctl perform failover pg_autoctl perform switchover pg_autoctl perform promotion * Implement suppor for environment variable PG_AUTOCTL_MONITOR.
1 parent b965460 commit 5ef1078

15 files changed

Lines changed: 710 additions & 239 deletions

src/bin/pg_autoctl/cli_common.c

Lines changed: 232 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,19 @@ cli_common_ensure_formation(KeeperConfig *options)
15831583
return true;
15841584
}
15851585

1586+
/*
1587+
* When --monitor has been used rather than --pgdata, we are operating at a
1588+
* distance and we don't expect a configuration file to exist.
1589+
*/
1590+
if (IS_EMPTY_STRING_BUFFER(options->pgSetup.pgdata))
1591+
{
1592+
strlcpy(options->formation,
1593+
FORMATION_DEFAULT,
1594+
sizeof(options->formation));
1595+
1596+
return true;
1597+
}
1598+
15861599
switch (ProbeConfigurationFileRole(options->pathnames.config))
15871600
{
15881601
case PG_AUTOCTL_ROLE_MONITOR:
@@ -1834,6 +1847,7 @@ cli_get_name_getopts(int argc, char **argv)
18341847

18351848
static struct option long_options[] = {
18361849
{ "pgdata", required_argument, NULL, 'D' },
1850+
{ "monitor", required_argument, NULL, 'm' },
18371851
{ "formation", required_argument, NULL, 'f' },
18381852
{ "name", required_argument, NULL, 'a' },
18391853
{ "json", no_argument, NULL, 'J' },
@@ -1876,6 +1890,19 @@ cli_get_name_getopts(int argc, char **argv)
18761890
break;
18771891
}
18781892

1893+
case 'm':
1894+
{
1895+
if (!validate_connection_string(optarg))
1896+
{
1897+
log_fatal("Failed to parse --monitor connection string, "
1898+
"see above for details.");
1899+
exit(EXIT_CODE_BAD_ARGS);
1900+
}
1901+
strlcpy(options.monitor_pguri, optarg, MAXCONNINFO);
1902+
log_trace("--monitor %s", options.monitor_pguri);
1903+
break;
1904+
}
1905+
18791906
case 'f':
18801907
{
18811908
strlcpy(options.formation, optarg, NAMEDATALEN);
@@ -1959,7 +1986,23 @@ cli_get_name_getopts(int argc, char **argv)
19591986
}
19601987

19611988
/* now that we have the command line parameters, prepare the options */
1962-
(void) prepare_keeper_options(&options);
1989+
/* when we have a monitor URI we don't need PGDATA */
1990+
if (cli_use_monitor_option(&options))
1991+
{
1992+
if (!IS_EMPTY_STRING_BUFFER(options.pgSetup.pgdata))
1993+
{
1994+
log_warn("Given --monitor URI, the --pgdata option is ignored");
1995+
log_info("Connecting to monitor at \"%s\"", options.monitor_pguri);
1996+
1997+
/* the rest of the program needs pgdata actually empty */
1998+
bzero((void *) options.pgSetup.pgdata,
1999+
sizeof(options.pgSetup.pgdata));
2000+
}
2001+
}
2002+
else
2003+
{
2004+
(void) prepare_keeper_options(&options);
2005+
}
19632006

19642007
/* ensure --formation, or get it from the configuration file */
19652008
if (!cli_common_ensure_formation(&options))
@@ -1975,6 +2018,79 @@ cli_get_name_getopts(int argc, char **argv)
19752018
}
19762019

19772020

2021+
/*
2022+
* cli_use_monitor_option returns true when the --monitor option should be
2023+
* used, or when PG_AUTOCTL_MONITOR has been set in the environment. In that
2024+
* case the options->monitor_pguri is also set to the value found in the
2025+
* environment.
2026+
*/
2027+
bool
2028+
cli_use_monitor_option(KeeperConfig *options)
2029+
{
2030+
/* if --monitor is used, then use it */
2031+
if (!IS_EMPTY_STRING_BUFFER(options->monitor_pguri))
2032+
{
2033+
return true;
2034+
}
2035+
2036+
/* otherwise, have a look at the PG_AUTOCTL_MONITOR environment variable */
2037+
if (env_exists(PG_AUTOCTL_MONITOR) &&
2038+
get_env_copy(PG_AUTOCTL_MONITOR,
2039+
options->monitor_pguri,
2040+
sizeof(options->monitor_pguri)))
2041+
{
2042+
log_debug("Using environment PG_AUTOCTL_MONITOR \"%s\"",
2043+
options->monitor_pguri);
2044+
return true;
2045+
}
2046+
2047+
/*
2048+
* Still nothing? well don't use --monitor then.
2049+
*
2050+
* Now, on commands that are compatible with using just a monitor and no
2051+
* local pg_autoctl node, we want to include an error message about the
2052+
* lack of a --monitor when we also lack --pgdata.
2053+
*/
2054+
if (IS_EMPTY_STRING_BUFFER(options->pgSetup.pgdata) &&
2055+
!env_exists("PGDATA"))
2056+
{
2057+
log_error("Failed to get value for environment variable '%s', "
2058+
"which is unset", PG_AUTOCTL_MONITOR);
2059+
log_warn("This command also supports the --monitor option, which "
2060+
"is not used here");
2061+
}
2062+
2063+
return false;
2064+
}
2065+
2066+
2067+
/*
2068+
* cli_monitor_init_from_option_or_config initialises a monitor connection
2069+
* either from the --monitor Postgres URI given on the command line, or from
2070+
* the configuration file of the local node (monitor or keeper).
2071+
*/
2072+
void
2073+
cli_monitor_init_from_option_or_config(Monitor *monitor, KeeperConfig *kconfig)
2074+
{
2075+
if (IS_EMPTY_STRING_BUFFER(kconfig->monitor_pguri))
2076+
{
2077+
if (!monitor_init_from_pgsetup(monitor, &(kconfig->pgSetup)))
2078+
{
2079+
/* errors have already been logged */
2080+
exit(EXIT_CODE_BAD_CONFIG);
2081+
}
2082+
}
2083+
else
2084+
{
2085+
if (!monitor_init(monitor, kconfig->monitor_pguri))
2086+
{
2087+
/* errors have already been logged */
2088+
exit(EXIT_CODE_BAD_ARGS);
2089+
}
2090+
}
2091+
}
2092+
2093+
19782094
/*
19792095
* cli_ensure_node_name ensures that we have a node name to continue with,
19802096
* either from the command line itself, or from the configuration file when
@@ -1983,40 +2099,141 @@ cli_get_name_getopts(int argc, char **argv)
19832099
void
19842100
cli_ensure_node_name(Keeper *keeper)
19852101
{
2102+
/* if we have a --name option, we're done already */
2103+
if (!IS_EMPTY_STRING_BUFFER(keeper->config.name))
2104+
{
2105+
return;
2106+
}
2107+
2108+
/* we might have --monitor instead of --pgdata */
2109+
if (IS_EMPTY_STRING_BUFFER(keeper->config.pgSetup.pgdata))
2110+
{
2111+
log_fatal("Please use either --name or --pgdata "
2112+
"to target a specific node");
2113+
exit(EXIT_CODE_BAD_ARGS);
2114+
}
2115+
19862116
switch (ProbeConfigurationFileRole(keeper->config.pathnames.config))
19872117
{
19882118
case PG_AUTOCTL_ROLE_MONITOR:
19892119
{
1990-
if (IS_EMPTY_STRING_BUFFER(keeper->config.name))
2120+
log_fatal("Please use --name to target a specific node");
2121+
exit(EXIT_CODE_BAD_ARGS);
2122+
break;
2123+
}
2124+
2125+
case PG_AUTOCTL_ROLE_KEEPER:
2126+
{
2127+
bool monitorDisabledIsOk = false;
2128+
2129+
if (!keeper_config_read_file_skip_pgsetup(&(keeper->config),
2130+
monitorDisabledIsOk))
19912131
{
1992-
log_fatal("Please use --name to target a specific node");
1993-
exit(EXIT_CODE_BAD_ARGS);
2132+
/* errors have already been logged */
2133+
exit(EXIT_CODE_BAD_CONFIG);
19942134
}
19952135
break;
19962136
}
19972137

1998-
case PG_AUTOCTL_ROLE_KEEPER:
2138+
default:
2139+
{
2140+
log_fatal("Unrecognized configuration file \"%s\"",
2141+
keeper->config.pathnames.config);
2142+
exit(EXIT_CODE_INTERNAL_ERROR);
2143+
}
2144+
}
2145+
}
2146+
2147+
2148+
/*
2149+
* cli_set_groupId sets the kconfig.groupId depending on the --group argument
2150+
* given on the command line, and if that was not given then figures it out:
2151+
*
2152+
* - it could be that we have a single group in the formation, in that case
2153+
* --group must be zero, so we set it that way,
2154+
*
2155+
* - we may have a local keeper node setup thanks to --pgdata, in that case
2156+
* read the configuration file and grab the groupId from there.
2157+
*/
2158+
void
2159+
cli_set_groupId(Monitor *monitor, KeeperConfig *kconfig)
2160+
{
2161+
int groupsCount = 0;
2162+
2163+
if (!monitor_count_groups(monitor, kconfig->formation, &groupsCount))
2164+
{
2165+
/* errors have already been logged */
2166+
exit(EXIT_CODE_MONITOR);
2167+
}
2168+
2169+
if (groupsCount == 0)
2170+
{
2171+
/* nothing to be done here */
2172+
log_fatal("The monitor currently has no Postgres nodes "
2173+
"registered in formation \"%s\"",
2174+
kconfig->formation);
2175+
exit(EXIT_CODE_BAD_STATE);
2176+
}
2177+
2178+
/*
2179+
* When --group was not given, we may proceed when there is only one
2180+
* possible target group in the formation, which is the case with Postgres
2181+
* standalone setups.
2182+
*/
2183+
if (kconfig->groupId == -1)
2184+
{
2185+
/*
2186+
* When --group is not given and we have a keeper node, we can grab a
2187+
* default from the configuration file. We have to support the usage
2188+
* either --monitor or --pgdata. We have a local keeper node/role only
2189+
* when we have been given --pgdata.
2190+
*/
2191+
if (!IS_EMPTY_STRING_BUFFER(kconfig->pgSetup.pgdata))
19992192
{
2000-
/* when --name has not been used, fetch it from the config */
2001-
if (IS_EMPTY_STRING_BUFFER(keeper->config.name))
2193+
pgAutoCtlNodeRole role =
2194+
ProbeConfigurationFileRole(kconfig->pathnames.config);
2195+
2196+
if (role == PG_AUTOCTL_ROLE_KEEPER)
20022197
{
2003-
bool monitorDisabledIsOk = false;
2198+
const bool missingPgdataIsOk = true;
2199+
const bool pgIsNotRunningIsOk = true;
2200+
const bool monitorDisabledIsOk = false;
20042201

2005-
if (!keeper_config_read_file_skip_pgsetup(&(keeper->config),
2006-
monitorDisabledIsOk))
2202+
if (!keeper_config_read_file(kconfig,
2203+
missingPgdataIsOk,
2204+
pgIsNotRunningIsOk,
2205+
monitorDisabledIsOk))
20072206
{
20082207
/* errors have already been logged */
20092208
exit(EXIT_CODE_BAD_CONFIG);
20102209
}
2210+
2211+
log_info("Targetting group %d in formation \"%s\"",
2212+
kconfig->groupId,
2213+
kconfig->formation);
20112214
}
2012-
break;
20132215
}
2216+
}
20142217

2015-
default:
2218+
/*
2219+
* We tried to see if we have a local keeper configuration to grab the
2220+
* groupId from, what if we don't have a local setup, or the local setup is
2221+
* not a keeper role.
2222+
*/
2223+
if (kconfig->groupId == -1)
2224+
{
2225+
if (groupsCount == 1)
20162226
{
2017-
log_fatal("Unrecognized configuration file \"%s\"",
2018-
keeper->config.pathnames.config);
2019-
exit(EXIT_CODE_INTERNAL_ERROR);
2227+
/* we have only one group, it's group number zero, proceed */
2228+
kconfig->groupId = 0;
2229+
kconfig->pgSetup.pgKind = NODE_KIND_STANDALONE;
2230+
}
2231+
else
2232+
{
2233+
log_error("Please use the --group option to target a "
2234+
"specific group in formation \"%s\"",
2235+
kconfig->formation);
2236+
exit(EXIT_CODE_BAD_ARGS);
20202237
}
20212238
}
20222239
}

src/bin/pg_autoctl/cli_common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ void set_first_pgctl(PostgresSetup *pgSetup);
167167
bool monitor_init_from_pgsetup(Monitor *monitor, PostgresSetup *pgSetup);
168168

169169
void exit_unless_role_is_keeper(KeeperConfig *kconfig);
170+
void cli_set_groupId(Monitor *monitor, KeeperConfig *kconfig);
170171

171172
/* cli_create_drop_node.c */
172173
bool cli_create_config(Keeper *keeper);
@@ -188,6 +189,9 @@ bool cli_pg_autoctl_reload(const char *pidfile);
188189

189190
int cli_node_metadata_getopts(int argc, char **argv);
190191
int cli_get_name_getopts(int argc, char **argv);
192+
bool cli_use_monitor_option(KeeperConfig *options);
193+
void cli_monitor_init_from_option_or_config(Monitor *monitor,
194+
KeeperConfig *kconfig);
191195
void cli_ensure_node_name(Keeper *keeper);
192196

193197
bool discover_hostname(char *hostname, int size,

src/bin/pg_autoctl/cli_do_tmux.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,17 +467,29 @@ tmux_add_xdg_environment(PQExpBuffer script)
467467
* tmux_setenv adds setenv commands to the tmux script.
468468
*/
469469
void
470-
tmux_setenv(PQExpBuffer script, const char *sessionName, const char *root)
470+
tmux_setenv(PQExpBuffer script,
471+
const char *sessionName, const char *root, int firstPort)
471472
{
472473
char PATH[BUFSIZE] = { 0 };
474+
char monitor_pguri[MAXCONNINFO] = { 0 };
473475

474476
if (!get_env_copy("PATH", PATH, sizeof(PATH)))
475477
{
476478
log_fatal("Failed to get PATH from the environment");
477479
exit(EXIT_CODE_INTERNAL_ERROR);
478480
}
479481

480-
tmux_add_command(script, "set-environment -t %s PATH \"%s\"", sessionName, PATH);
482+
tmux_add_command(script,
483+
"set-environment -t %s PATH \"%s\"", sessionName, PATH);
484+
485+
sformat(monitor_pguri, sizeof(monitor_pguri),
486+
"postgres://autoctl_node@localhost:%d/pg_auto_failover?sslmode=prefer",
487+
firstPort);
488+
489+
tmux_add_command(script,
490+
"set-environment -t %s PG_AUTOCTL_MONITOR \"%s\"",
491+
sessionName,
492+
monitor_pguri);
481493

482494
for (int i = 0; xdg[i][0] != NULL; i++)
483495
{
@@ -635,7 +647,7 @@ tmux_pg_autoctl_create_postgres(PQExpBuffer script,
635647
tmux_add_send_keys_command(script, "export PGPORT=%d", pgport);
636648

637649
sformat(monitor, sizeof(monitor),
638-
"$(%s show uri --pgdata %s/monitor --monitor)",
650+
"$(%s show uri --pgdata %s/monitor --formation monitor)",
639651
pg_autoctl_argv0,
640652
root);
641653

@@ -680,7 +692,7 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
680692
tmux_add_command(script, "set-option -g default-shell /bin/bash");
681693

682694
(void) tmux_add_new_session(script, root, pgport);
683-
(void) tmux_setenv(script, sessionName, root);
695+
(void) tmux_setenv(script, sessionName, root, options->firstPort);
684696

685697
/* start a monitor */
686698
(void) tmux_add_xdg_environment(script);
@@ -715,7 +727,6 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
715727
node->name,
716728
node->replicationQuorum,
717729
node->candidatePriority);
718-
tmux_add_send_keys_command(script, "pg_autoctl run");
719730

720731
strlcpy(previousName, node->name, sizeof(previousName));
721732
}
@@ -740,7 +751,6 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
740751
tmux_add_command(script, "split-window -v");
741752
tmux_add_command(script, "select-layout even-vertical");
742753
(void) tmux_add_xdg_environment(script);
743-
tmux_add_send_keys_command(script, "export PGDATA=\"%s/monitor\"", root);
744754

745755
if (options->numSync != -1)
746756
{

0 commit comments

Comments
 (0)