@@ -42,6 +42,7 @@ char *tmux_banner[] = {
4242};
4343
4444TmuxOptions tmuxOptions = { 0 };
45+ TmuxNodeArray tmuxNodeArray = { 0 };
4546
4647char * xdg [][3 ] = {
4748 { "XDG_DATA_HOME" , "share" },
@@ -52,6 +53,141 @@ char *xdg[][3] = {
5253
5354static void prepare_tmux_script (TmuxOptions * options , PQExpBuffer script );
5455static 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 ,
0 commit comments