Skip to content

Commit 748ab2e

Browse files
authored
make NODES=3 NODES_ASYNC=2 NODES_PRIOS=50,0 NODES_SYNC_SB=0 cluster (#551)
* make NODES=3 NODES_ASYNC=2 NODES_PRIOS=50,0 NODES_SYNC_SB=0 cluster This PR allows to setup an interactive testing/QA cluster with tmux with full specifications of nodes replication settings. Before that the code was conflating the replicationQuorum and the candidatePriority of nodes, and that was limiting our testing abilities. When unspecified, nodes are all created with --candidate-priority 0. Then it is possible to specify one or more candidate priorities, which are used in the order given. When more nodes are created than priorities are given, we use the last specified priority again and again. For instance --node-priorities 50,0 --nodes 3 results in using 50,0,0 as the respective priorities for the nodes thus created.
1 parent d1f9e4d commit 748ab2e

3 files changed

Lines changed: 220 additions & 27 deletions

File tree

Makefile

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ PDF = ./docs/_build/latex/pg_auto_failover.pdf
3030
# Command line with DEBUG facilities
3131
PG_AUTOCTL = PG_AUTOCTL_DEBUG=1 ./src/bin/pg_autoctl/pg_autoctl
3232

33-
# make cluster arguments
34-
NODES ?= 2
35-
NODES_ASYNC ?= 0
33+
NODES ?= 2 # total count of Postgres nodes
34+
NODES_ASYNC ?= 0 # count of replication-quorum false nodes
35+
NODES_PRIOS ?= 50 # either "50", or "50,50", or "50,50,0" etc
3636
NODES_SYNC_SB ?= -1
3737
FIRST_PGPORT ?= 5500
3838

@@ -148,21 +148,25 @@ $(TMUX_SCRIPT): bin
148148
--first-pgport $(FIRST_PGPORT) \
149149
--nodes $(NODES) \
150150
--async-nodes $(NODES_ASYNC) \
151+
--node-priorities $(NODES_PRIOS) \
151152
--sync-standbys $(NODES_SYNC_SB) \
152153
--layout $(TMUX_LAYOUT) > $@
153154

154155
tmux-script: $(TMUX_SCRIPT) ;
155156

156157
tmux-clean:
157-
pkill pg_autoctl || true
158-
rm -rf $(TMUX_TOP_DIR)
158+
$(PG_AUTOCTL) do tmux clean \
159+
--root $(TMUX_TOP_DIR) \
160+
--first-pgport $(FIRST_PGPORT) \
161+
--nodes $(NODES)
159162

160-
cluster: install
163+
cluster: install tmux-clean
161164
$(PG_AUTOCTL) do tmux session \
162165
--root $(TMUX_TOP_DIR) \
163166
--first-pgport $(FIRST_PGPORT) \
164167
--nodes $(NODES) \
165168
--async-nodes $(NODES_ASYNC) \
169+
--node-priorities $(NODES_PRIOS) \
166170
--sync-standbys $(NODES_SYNC_SB) \
167171
--layout $(TMUX_LAYOUT)
168172

src/bin/pg_autoctl/cli_do_tmux.c

Lines changed: 189 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ char *tmux_banner[] = {
4242
};
4343

4444
TmuxOptions tmuxOptions = { 0 };
45+
TmuxNodeArray tmuxNodeArray = { 0 };
4546

4647
char *xdg[][3] = {
4748
{ "XDG_DATA_HOME", "share" },
@@ -52,6 +53,141 @@ char *xdg[][3] = {
5253

5354
static void prepare_tmux_script(TmuxOptions *options, PQExpBuffer script);
5455
static bool tmux_stop_pg_autoctl(TmuxOptions *options);
56+
static bool parseCandidatePriority(char *priorityString,
57+
int pIndex, int *priorities);
58+
static bool prepareTmuxNodeArray(TmuxOptions *options, TmuxNodeArray *nodeArray);
59+
60+
61+
/*
62+
* parseCandidatePriority parses a single candidate priority item into given
63+
* index in the priorities integer array of MAX_NODES capacity
64+
*/
65+
static bool
66+
parseCandidatePriority(char *priorityString, int pIndex, int *priorities)
67+
{
68+
if (MAX_NODES <= pIndex)
69+
{
70+
log_error("Failed to parse --node-priorities: "
71+
"pg_autoctl do tmux session supports up to %d nodes",
72+
MAX_NODES);
73+
return false;
74+
}
75+
76+
if (!stringToInt(priorityString, &(priorities[pIndex])))
77+
{
78+
log_error("Failed to parse --node-priorities \"%s\"", priorityString);
79+
return false;
80+
}
81+
82+
log_trace("parseCandidatePriorities[%d] = %d", pIndex, priorities[pIndex]);
83+
84+
return true;
85+
}
86+
87+
88+
/*
89+
* parseCandidatePriorities parses the --node-priorities options on the command
90+
* line and fills-in an array of nodes.
91+
*
92+
* --node-priorities 50: all node have 50
93+
* --node-priorities 50,50,0: 3+ nodes, first two have 50, then 0
94+
*/
95+
bool
96+
parseCandidatePriorities(char *prioritiesString, int *priorities)
97+
{
98+
char sep = ',';
99+
char *ptr = prioritiesString;
100+
char *previous = prioritiesString;
101+
102+
int pIndex = 0;
103+
104+
if (strcmp(prioritiesString, "") == 0)
105+
{
106+
/* fill-in the priorities array with default values (50) */
107+
for (int i = 0; i < MAX_NODES; i++)
108+
{
109+
priorities[i] = FAILOVER_NODE_CANDIDATE_PRIORITY;
110+
}
111+
return true;
112+
}
113+
114+
while ((ptr = strchr(ptr, sep)) != NULL)
115+
{
116+
*ptr = '\0';
117+
118+
if (!parseCandidatePriority(previous, pIndex++, priorities))
119+
{
120+
/* errors have already been logged */
121+
return false;
122+
}
123+
124+
previous = ++ptr;
125+
}
126+
127+
/* there is no separator left, parse the end of the option string */
128+
if (!parseCandidatePriority(previous, pIndex++, priorities))
129+
{
130+
/* errors have already been logged */
131+
return false;
132+
}
133+
134+
/* mark final entry in the array; remember that pIndex > 0 here */
135+
for (int i = pIndex; i < MAX_NODES; i++)
136+
{
137+
priorities[i] = priorities[i - 1];
138+
}
139+
140+
return true;
141+
}
142+
143+
144+
/*
145+
* prepareTmuxNodeArray expands the command line options into an array of
146+
* nodes, where each node name and properties have been computed.
147+
*/
148+
bool
149+
prepareTmuxNodeArray(TmuxOptions *options, TmuxNodeArray *nodeArray)
150+
{
151+
/* first pgport is for the monitor */
152+
int pgport = options->firstPort + 1;
153+
154+
/* make sure we initialize our nodes array */
155+
nodeArray->count = 0;
156+
nodeArray->numSync = options->numSync;
157+
158+
for (int i = 0; i < options->nodes; i++)
159+
{
160+
TmuxNode *node = &(nodeArray->nodes[i]);
161+
162+
sformat(node->name, sizeof(node->name), "node%d", i + 1);
163+
164+
node->pgport = pgport++;
165+
166+
/* the first nodes are sync, then async, threshold is asyncNodes */
167+
node->replicationQuorum = i < (options->nodes - options->asyncNodes);
168+
169+
/* node priorities have been expanded correctly in the options */
170+
node->candidatePriority = options->priorities[i];
171+
172+
++(nodeArray->count);
173+
}
174+
175+
176+
/* some useful debug information */
177+
for (int i = 0; i < options->nodes; i++)
178+
{
179+
TmuxNode *node = &(nodeArray->nodes[i]);
180+
181+
log_debug("prepareTmuxNodeArray[%d]: %s %d %s %d",
182+
i,
183+
node->name,
184+
node->pgport,
185+
node->replicationQuorum ? "true" : "false",
186+
node->candidatePriority);
187+
}
188+
189+
return true;
190+
}
55191

56192

57193
/*
@@ -72,6 +208,7 @@ cli_do_tmux_script_getopts(int argc, char **argv)
72208
{ "first-pgport", required_argument, NULL, 'p' },
73209
{ "nodes", required_argument, NULL, 'n' },
74210
{ "async-nodes", required_argument, NULL, 'a' },
211+
{ "node-priorities", required_argument, NULL, 'P' },
75212
{ "sync-standbys", required_argument, NULL, 's' },
76213
{ "layout", required_argument, NULL, 'l' },
77214
{ "version", no_argument, NULL, 'V' },
@@ -91,6 +228,12 @@ cli_do_tmux_script_getopts(int argc, char **argv)
91228
strlcpy(options.root, "/tmp/pgaf/tmux", sizeof(options.root));
92229
strlcpy(options.layout, "even-vertical", sizeof(options.layout));
93230

231+
if (!parseCandidatePriorities("", options.priorities))
232+
{
233+
log_error("BUG: failed to initialize candidate priorities");
234+
exit(EXIT_CODE_INTERNAL_ERROR);
235+
}
236+
94237
/*
95238
* The only command lines that are using keeper_cli_getopt_pgdata are
96239
* terminal ones: they don't accept subcommands. In that case our option
@@ -133,6 +276,15 @@ cli_do_tmux_script_getopts(int argc, char **argv)
133276
optarg);
134277
errors++;
135278
}
279+
280+
if (MAX_NODES < options.nodes)
281+
{
282+
log_error("pg_autoctl do tmux session supports up to %d "
283+
"nodes, and --nodes %d has been asked for",
284+
MAX_NODES, options.nodes);
285+
errors++;
286+
}
287+
136288
log_trace("--nodes %d", options.nodes);
137289
break;
138290
}
@@ -149,6 +301,22 @@ cli_do_tmux_script_getopts(int argc, char **argv)
149301
break;
150302
}
151303

304+
case 'P':
305+
{
306+
char priorities[BUFSIZE] = { 0 };
307+
308+
/* parsing mangles the string, keep a copy */
309+
strlcpy(priorities, optarg, sizeof(priorities));
310+
311+
if (!parseCandidatePriorities(priorities, options.priorities))
312+
{
313+
log_error("Failed to parse --node-priorities \"%s\"",
314+
optarg);
315+
errors++;
316+
}
317+
break;
318+
}
319+
152320
case 's':
153321
{
154322
if (!stringToInt(optarg, &options.numSync))
@@ -223,6 +391,12 @@ cli_do_tmux_script_getopts(int argc, char **argv)
223391
}
224392
}
225393

394+
if (!prepareTmuxNodeArray(&options, &tmuxNodeArray))
395+
{
396+
/* errors have already been logged */
397+
exit(EXIT_CODE_BAD_ARGS);
398+
}
399+
226400
if (errors > 0)
227401
{
228402
commandline_help(stderr);
@@ -512,14 +686,9 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
512686
/* start the Postgres nodes, using the monitor URI */
513687
sformat(previousName, sizeof(previousName), "monitor");
514688

515-
for (int i = 0; i < options->nodes; i++)
689+
for (int i = 0; i < tmuxNodeArray.count; i++)
516690
{
517-
char name[NAMEDATALEN] = { 0 };
518-
519-
bool replicationQuorum = true;
520-
int candidatePriority = 50;
521-
522-
sformat(name, sizeof(name), "node%d", i + 1);
691+
TmuxNode *node = &(tmuxNodeArray.nodes[i]);
523692

524693
tmux_add_command(script, "split-window -v");
525694
tmux_add_command(script, "select-layout even-vertical");
@@ -537,19 +706,15 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
537706
options->root,
538707
previousName);
539708

540-
/* the first nodes are sync, then async, threshold is asyncNodes */
541-
if ((options->nodes - options->asyncNodes) <= i)
542-
{
543-
replicationQuorum = false;
544-
candidatePriority = 0;
545-
}
546-
547-
tmux_pg_autoctl_create_postgres(script, root, pgport++, name,
548-
replicationQuorum,
549-
candidatePriority);
709+
tmux_pg_autoctl_create_postgres(script,
710+
root,
711+
node->pgport,
712+
node->name,
713+
node->replicationQuorum,
714+
node->candidatePriority);
550715
tmux_add_send_keys_command(script, "pg_autoctl run");
551716

552-
strlcpy(previousName, name, sizeof(previousName));
717+
strlcpy(previousName, node->name, sizeof(previousName));
553718
}
554719

555720
/* add a window for pg_autoctl show state */
@@ -577,10 +742,13 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
577742
if (options->numSync != -1)
578743
{
579744
/*
580-
* We need to wait until the first node is PRIMARY before we can go on
581-
* and change formation settings with pg_autoctl set formation ...
745+
* We need to wait until the first node is either WAIT_PRIMARY or
746+
* PRIMARY before we can go on and change formation settings with
747+
* pg_autoctl set formation ...
582748
*/
583749
char firstNode[NAMEDATALEN] = { 0 };
750+
NodeState targetPrimaryState =
751+
options->numSync == 0 ? WAIT_PRIMARY_STATE : PRIMARY_STATE;
584752

585753
sformat(firstNode, sizeof(firstNode), "node%d", 1);
586754

@@ -590,7 +758,7 @@ prepare_tmux_script(TmuxOptions *options, PQExpBuffer script)
590758
pg_autoctl_argv0,
591759
options->root,
592760
firstNode,
593-
NodeStateToString(PRIMARY_STATE));
761+
NodeStateToString(targetPrimaryState));
594762

595763
/* PGDATA has just been exported, rely on it */
596764
tmux_add_send_keys_command(script,

src/bin/pg_autoctl/cli_do_tmux.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,38 @@
2626
#include "signals.h"
2727
#include "string_utils.h"
2828

29+
#define MAX_NODES 12
30+
2931
typedef struct TmuxOptions
3032
{
3133
char root[MAXPGPATH];
3234
int firstPort;
3335
int nodes; /* number of nodes per groups, total */
3436
int asyncNodes; /* number of async nodes, within the total */
37+
int priorities[MAX_NODES]; /* node priorities */
3538
int numSync; /* number-sync-standbys */
3639
char layout[BUFSIZE];
3740
} TmuxOptions;
3841

42+
typedef struct TmuxNode
43+
{
44+
char name[NAMEDATALEN];
45+
int pgport;
46+
bool replicationQuorum;
47+
int candidatePriority;
48+
} TmuxNode;
49+
50+
typedef struct TmuxNodeArray
51+
{
52+
int count; /* array actual size */
53+
int numSync; /* number-sync-standbys */
54+
TmuxNode nodes[MAX_NODES];
55+
} TmuxNodeArray;
56+
3957
extern TmuxOptions tmuxOptions;
58+
extern TmuxNodeArray tmuxNodeArray;
59+
60+
bool parseCandidatePriorities(char *prioritiesString, int *priorities);
4061

4162
void tmux_add_command(PQExpBuffer script, const char *fmt, ...)
4263
__attribute__((format(printf, 2, 3)));

0 commit comments

Comments
 (0)