]> git.8kb.co.uk Git - slony-i/slony_failover/blob - slony_failover.pl
* Did some brief testing with Slony 1.2, 2.0 and 2.1 and appears to all work as expected.
[slony-i/slony_failover] / slony_failover.pl
1 #!/usr/bin/perl
2
3 # Script:       failover.pl
4 # Copyright:    08/04/2012: v1.0.2 Glyn Astill <glyn@8kb.co.uk>
5 # Requires:     Perl 5.10.1+, Data::UUID, File::Slurp
6 #               PostgreSQL 9.0+ Slony-I 1.2+ / 2.0+
7 #
8 # This script is a command-line utility to manage switchover and failover
9 # of replication sets in Slony-I clusters.
10 #
11 # This script is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This script is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this script.  If not, see <http://www.gnu.org/licenses/>.
23
24 use strict;
25 use warnings;
26 use DBI;
27 use Getopt::Long qw/GetOptions/;
28 use Data::UUID;
29 use File::Slurp;
30 use v5.10.1;
31 use sigtrap 'handler' => \&sigExit, 'HUP', 'INT','ABRT','QUIT','TERM';
32 use Time::HiRes qw/usleep gettimeofday/;
33 use POSIX qw/strftime/;
34 use Config qw/%Config/;
35
36 use constant false => 0;
37 use constant true  => 1;
38
39 my $g_script_version = '1.0.2';
40 my $g_debug = false;
41 my $g_pidfile = '/var/run/slony_failover.pid';
42 my $g_pid_written = false;
43 my $g_lang = 'en';
44 my $g_prefix = '/tmp/slony_failovers';
45 my $g_separate_working = true;
46 my $g_log_prefix = '%t';
47 my $g_log_to_db = false;
48 my $g_logdb_name;
49 my $g_logdb_host;
50 my $g_logdb_port;
51 my $g_logdb_user;
52 my $g_logdb_pass;
53 my $g_slonikpath;
54 my $g_use_try_blocks = false;
55 my $g_lockset_method = 'multiple';
56 my $g_logfile = 'failover.log';
57 my $g_input;
58 my $g_silence_notice = false;
59 my $g_reason;
60 my $g_script;
61 my $g_node_from;
62 my $g_node_to;
63 my $g_clname;
64 my $g_dbname;
65 my $g_dbhost;
66 my $g_dbport = 5432;
67 my $g_dbconninfo;
68 my $g_dbpass = '';
69 my $g_dbuser = 'slony';
70 my $g_node_count;
71 my $g_available_node_count;
72 my $g_critical_node_count;
73 my $g_subs_follow_origin = false;
74 my $g_use_comment_aliases = false;
75 my @g_cluster;                      # Array refs of node info.  In hindsight this should have been a hash, should be fairly simple to switch.
76 my @g_failed;
77 my @g_sets;
78 my @g_lags;
79 my $g_result;
80 my $g_version;
81 my $g_failover_method = 'old';
82 my $g_resubscribe_method = 'subscribe';
83 my $g_failover = false;
84 my $g_fail_subonly = false;
85 my $g_drop_failed = false;
86 my $g_autofailover = false;
87 my $g_autofailover_poll_interval = 500;
88 my $g_autofailover_retry = 2;
89 my $g_autofailover_retry_sleep = 1000;
90 my $g_autofailover_provs = false;
91 my $g_autofailover_config_any = true;
92 my $g_autofailover_perspective_sleep = 20000;
93 my $g_autofailover_majority_only = false;
94 my $g_autofailover_is_quorum = false;
95 my @g_unresponsive;
96 my %g_backups;
97 my $g_pid = $$;
98
99
100 my %message = (
101 'en' => {
102     'usage'                            => q{-h <host> -p <port> -db <database> -cl <cluster name> -u <username> -P <password> -f <config file> (Password option not recommended; use pgpass instead)},
103     'title'                            => q{Slony-I failover script version $1},
104     'cluster_fixed'                    => q{Aborting failover action: all origin nodes now responsive},
105     'cluster_failed'                   => q{Found $1 failed nodes, sleeping for $2ms before retry $3 of $4},
106     'load_cluster'                     => q{Getting a list of database nodes...}, 
107     'load_cluster_fail'                => q{Unable to read cluster configuration $1}, 
108     'load_cluster_success'             => q{Loaded Slony-I v$1 cluster "$2" with $3 nodes read from node at $4:$5/$6}, 
109     'lag_detail'                       => q{Current node lag information from configuration node:},
110     'script_settings'                  => q{Using $1 batches of lock set, $2 FAILOVER and $3},
111     'generated_script'                 => q{Generated script "$1"},
112     'autofailover_init'                => q{Entering autofailover mode},
113     'autofailover_init_cnf'            => q{Slony configuration will be read from $1 node},
114     'autofailover_init_pol'            => q{Polling every $1ms},
115     'autofailover_init_ret'            => q{Failed nodes will be retried $1 times with $2ms sleep},
116     'autofailover_init_set'            => q{Failed forwarding providers $1 be failed over},
117     'autofailover_load_cluster'        => q{$1 Slony-I v$2 cluster "$3" with $4 nodes},
118     'autofailover_proceed'             => q{Proceeding with failover:},
119     'autofailover_detail'              => q{Failed node: $1, Backup node: $2},
120     'autofailover_halt'                => q{Unable to perform any failover for $1 failed nodes},
121     'autofailover_check_sub'           => q{Checking subscriber node $1},
122     'autofailover_check_sub_fail'      => q{Unable to check subscriber node $1},
123     'autofailover_promote_find'        => q{Finding most up to date subscriber to all sets ($1) on unresponsive node $2},
124     'autofailover_promote_found'       => q{Using previously found most up to date subscriber to all sets ($1) on unresponsive node $2},
125     'autofailover_promote_skip'        => q{No failover required for unresponsive node $1 as it is neither the origin or an active forwarder of any sets},
126     'autofailover_promote_fail'        => q{Could not find suitable backup node for promotion},
127     'autofailover_node_detail'         => q{Node $1 is $2 subscribed to ($3) node $4 and provides sets $5 at $6 lag ($7 events)},
128     'autofailover_node_detail_subonly' => q{Node $1 is $2 subscribed to ($3) node $4 and is a subscriber only at $5 lag ($6 events)},
129     'autofailover_promote_best'        => q{Best node for promotion is node $1 seq = $2 ($3 events)},
130     'autofailover_unresponsive'        => q{Detected unresponsive provider node: $1},
131     'autofailover_unresponsive_subonly'=> q{Detected unresponsive subscriber only node: $1},
132     'autofailover_pspec_check_fail'    => q{Failed to connect to node $1: $2},
133     'autofailover_pspec_check'         => q{Getting objective judgement from other nodes, apparent unresponsive nodes are : $1 (Failed nodes = $2 of $3)},
134     'autofailover_pspec_check_sleep'   => q{Sleeping for $1 ms},
135     'autofailover_pspec_check_data'    => q{$1: Node $2 says lag from node $3 -> $4 is $5 seconds},
136     'autofailover_pspec_check_true'    => q{All detected failed nodes confirmed as lagging by other nodes},
137     'autofailover_pspec_check_false'   => q{Not all nodes confirmed as lagging},
138     'autofailover_pspec_check_unknown' => q{Unable to confirm lag status of all nodes},
139     'autofailover_split_check'         => q{Surviving nodes ($1 of $2) are the majority},
140     'autofailover_split_check_fail'    => q{Surviving nodes ($1) are not the majority},
141     'interactive_head_id'              => q{ID},
142     'interactive_head_name'            => q{Name},
143     'interactive_head_status'          => q{Status},
144     'interactive_head_providers'       => q{Provider IDs},
145     'interactive_head_config'          => q{Configuration},
146     'interactive_detail_1'             => q{Origin for sets: },
147     'interactive_detail_2'             => q{Providing sets: },
148     'interactive_detail_3'             => q{Subscriptions: },
149     'interactive_choose_node'          => q{Please choose the node to move all sets $1:},
150     'interactive_confirm'              => q{You chose to move sets $1 node $2 ($3). Is this correct [y/n]? },
151     'interactive_action'               => q{Best course of action is most likely to do a "$1". Do you wish to continue [y/n]?},
152     'interactive_surrender'            => q{Uable to determine best course of action},
153     'interactive_write_script'         => q{Writing a script to $1 node $2 to $3},
154     'interactive_check_nodes'          => q{Checking availability of database nodes...},
155     'interactive_continue'             => q{Do you wish to continue [y/n]?},
156     'interactive_drop_nodes'           => q{Do you want to also drop the failed nodes from the slony configuration [y/n]?},
157     'interactive_preserve'             => q{Preserve subscription paths to follow the origin node (choose no if unsure) [y/n]?},
158     'interactive_aliases'              => q{Generate aliases based on sl_node/set comments in parentheses (choose no if unsure) [y/n]?},
159     'interactive_summary'              => q{Summary of nodes to be passed to failover:},
160     'interactive_node_info'            => q{Node : $1 ($2) $3 (conninfo $4)},
161     'interactive_run_script'           => q{Would you like to run this script now [y/n]?},
162     'interactive_running'              => q{Running the script now. This may take some time; please be patient!},
163     'interactive_reason'               => q{Please enter a brief reson for taking this action: },
164     'interactive_failover_detail_1'    => q{Before you go any further please consider the impact of a full failover:},
165     'interactive_failover_detail_2'    => q{The node you are failing over from will cease to participate in the cluster permanently until it is rebuild & subscribed},
166     'interactive_failover_detail_3'    => q{If the outage is temporary (i.e. network/power/easily replaceable hardware related) consider waiting it out},
167     'interactive_failover_detail_4'    => q{This type of failover is likely to be more a business decision than a technical one},
168     'info_all_nodes_available'         => q{INFO: All nodes are available},
169     'info_req_nodes_available'         => q{INFO: $1 of $2 nodes are available. No unavailable nodes are subscribed to the old origin},
170     'wrn_node_unavailable'             => q{WARNING: Node $1 unavailable},
171     'wrn_req_unavailable'              => q{WARNING: Old origin node ($1) is available, however $2 subscribers are unavailable},
172     'wrn_not_tested'                   => q{WARNING: Script not tested with Slony-I v$1},
173     'wrn_failover_issues'              => q{WARNING: Slony-I v$1 may struggle to failover correctly with multiple failed nodes (affects v2.0-2.1)},
174     'note_autofail_fwd_only'           => q{NOTICE: Slony versions prior to 2.2 cannot initiate failover from only failed forwarding providers},
175     'note_fail_sub_only'               => q{NOTICE: Slony versions prior to 2.2 cannot failover subscriber only nodes, reverting to failover_offline_subscriber_only = false},
176     'note_multiple_try'                => q{NOTICE: Cannot lock multiple sets within try blocks in version $1 dropping back to single sets},
177     'note_reshape_cluster'             => q{NOTICE: Either drop the failed subscribers or bring them back up, then retry to MOVE SET},
178     'dbg_generic'                      => q{DEBUG: $1},
179     'dbg_cluster'                      => q{DEBUG: NodeID $1/ProvIDs $2/Conninfo $3/OrigSets $4/NodeName $5/ProvTree $6/Active $7/FwdSets $8/ActSubSets $9},
180     'dbg_resubscribe'                  => q{DEBUG: Checking possibility to resubscribe set $1 ($2) to node $3 ($4) which pulls $5 ($6) from $7 ($8)},
181     'dbg_failover_method'              => q{DEBUG: Failover method is $1},
182     'dbg_cluster_load'                 => q{DEBUG: Loading cluster configuration from $1},
183     'dbg_cluster_good'                 => q{DEBUG: Cluster state good},
184     'dbg_autofailover_check'           => q{DEBUG: Checking node $1 ($2) role is $3 (conninfo: $4)},
185     'dbg_autofailover_active_check'    => q{DEBUG: Initiate active check of $1 node $2},
186     'dbg_slonik_script'                => q{DEBUG: Running slonik script $1},
187     'err_generic'                      => q{ERROR: $1},
188     'err_no_database'                  => q{ERROR: Please specify a database name},
189     'err_no_cluster'                   => q{ERROR: Please specify a slony cluster name},
190     'err_no_host'                      => q{ERROR: Please specify a host},
191     'err_no_config'                    => q{ERROR: No valid config found},
192     'err_fail_config'                  => q{ERROR: Failed to load configuration},
193     'err_write_fail'                   => q{ERROR: Could not write to $1 "$2"},
194     'err_read_fail'                    => q{ERROR: Could not read from $1 "$2"},
195     'err_unlink_fail'                  => q{ERROR: Could not delete $1 "$2"},
196     'err_mkdir_fail'                   => q{ERROR: Unable to create $1 directory "$2"},
197     'err_execute_fail'                 => q{ERROR: Could not execute $1 "$2"},
198     'err_inactive'                     => q{ERROR: Node $1 is not active (state = $2)},
199     'err_cluster_empty'                => q{ERROR: Loaded cluster contains no nodes}, 
200     'err_cluster_offline'              => q{ERROR: Loaded cluster contains no reachable nodes}, 
201     'err_cluster_lone'                 => q{ERROR: Loaded cluster contains only 1 node}, 
202     'err_not_origin'                   => q{ERROR: Node $1 is not the origin of any sets},
203     'err_not_provider'                 => q{ERROR: Node $1 is not a provider of any sets},
204     'err_not_provider_sets'            => q{ERROR: Node $1 does not provide the sets required: need ($2) but provides ($3)},
205     'err_no_configuration'             => q{ERROR: Could not read configuration for node $1},
206     'err_must_enter_node_id'           => q{ERROR: You must enter a node id},
207     'err_not_a_node_id'                => q{ERROR: I have no knowledge of a node $1},
208     'err_same_node'                    => q{ERROR: Cant move from and to the same node},
209     'err_node_offline'                 => q{ERROR: $1 node ($2) is not available},
210     'err_incomplete_preamble'          => q{ERROR: Incomplete preamble},
211     'err_running_slonik'               => q{ERROR: Could not run slonik: $1},
212     'err_pgsql_connect'                => q{ERROR: Cannot connect to postgres server},
213     'slonik_output'                    => q{SLONIK: $1},
214     'exit_noaction'                    => q{Exiting, no action has been taken},
215     'exit'                             => q{Exited by $1}
216     },
217 'fr' => {
218     'usage'                            => q{-h <host> -p <port> -db <database> -cl <cluster name> -u <username> -P <password> -f <config file> (Option mot de passe pas recommandé; utiliser pgpass place)},
219     'title'                            => q{Slony-I failover (basculement) version de script $1},
220     'cluster_fixed'                    => q{Abandon de l'action de basculement: tous les noeuds d'origine maintenant sensible},
221     'cluster_failed'                   => q{Trouvé $1 Ã©choué noeuds, couchage pour $2 ms avant réessayer $3 de $4},
222     'load_cluster'                     => q{Obtenir une liste de noeuds de base de donnees...},
223     'load_cluster_fail'                => q{Impossible de lire la configuration du cluster $1},
224     'load_cluster_success'             => q{Chargé Slony-I v$1 groupe "$2" avec $3 noeuds lire Ã  partir du noeud Ã  $4:$5/$6},
225     'lag_detail'                       => q{Current informations noeud de décalage Ã  partir du noeud de configuration:},
226     'script_settings'                  => q{Utilisation de $1 lots de système de verrouillage, $2 FAILOVER et $3},
227     'generated_script'                 => q{Script généré "$1"},
228     'autofailover_init'                => q{Entrer dans le mode de autofailover},
229     'autofailover_init_cnf'            => q{Configuration Slony sera lu Ã  partir de $1 noeud},
230     'autofailover_init_pol'            => q{Vérifier toutes les $1ms},
231     'autofailover_init_ret'            => q{Noeuds défaillants seront rejugés $1 fois avec $2 ms sommeil},
232     'autofailover_init_set'            => q{Fournisseurs d'expédition Ã©choué $1 Ãªtre Ã©choué sur},
233     'autofailover_load_cluster'        => q{$1 Slony-I v$2 grappe "$3" avec $4 noeuds},
234     'autofailover_proceed'             => q{De procéder Ã  failover:},
235     'autofailover_detail'              => q{Noeud défaillant: $1, noeud de sauvegarde: $2},
236     'autofailover_halt'                => q{Noeuds Impossible d'effectuer une failover pour $1 Ã©choué},
237     'autofailover_check_sub'           => q{Vérification noeud abonné $1},
238     'autofailover_check_sub_fail'      => q{Impossible de vérifier noeud abonné $1},
239     'autofailover_promote_find'        => q{Trouver plus Ã  jour abonné Ã  tous les jeux ($1) sur le noeud ne répond pas $2},
240     'autofailover_promote_found'       => q{Utilisation précédemment trouvé plus Ã  jour abonné Ã  tous les jeux ($1) sur le noeud ne répond pas $2},
241     'autofailover_promote_skip'        => q{Pas de failover requis pour le noeud ne répond pas $1 car il n'est ni l'origine ou un transitaire active de tous les jeux},
242     'autofailover_promote_fail'        => q{Impossible de trouver le noeud de sauvegarde approprié pour la promotion},
243     'autofailover_node_detail'         => q{Noeud $1 est souscrit Ã  $2 ($3) noeud $4 et fournit des ensembles de $5 Ã  retard $6 ($7  Ã©vénements)},
244     'autofailover_node_detail_subonly' => q{Noeud $1 est souscrit Ã  $2 ($3) et le noeud $4 est un abonné Ã  retard $5 ($6 Ã©vénements)},
245     'autofailover_promote_best'        => q{Meilleur noeud pour la promotion est noeud $1 suivants = $2 ($3 Ã©vénements)},
246     'autofailover_unresponsive'        => q{Noeud ne répond pas détecté: $1},
247     'autofailover_unresponsive_subonly'=> q{Abonné ne répond pas détecté seulement de noeud: $1},
248     'autofailover_pspec_check_fail'    => q{Impossible de se connecter au noeud $1: $2},
249     'autofailover_pspec_check'         => q{Obtenir un jugement objectif Ã  partir d'autres noeuds, les noeuds qui ne répondent pas apparentes sont : $1 (Noeuds défaillants = $2 de $3)},
250     'autofailover_pspec_check_sleep'   => q{Dormir pour $1 ms},
251     'autofailover_pspec_check_data'    => q{$1: Noeud $2 dit décalage de $3 -> $4 noeud est $5 secondes},
252     'autofailover_pspec_check_true'    => q{Tous les noeuds détectés pas confirmés comme Ã  la traîne par d'autres noeuds},
253     'autofailover_pspec_check_false'   => q{Pas tous les noeuds confirmé retard},
254     'autofailover_pspec_check_unknown' => q{Impossible de confirmer le statut de latence de tous les noeuds},
255     'autofailover_split_check'         => q{Autres noeuds ($1 sur $2) sont la majorité},
256     'autofailover_split_check_fail'    => q{Autres noeuds ($1) ne sont pas la majorité},
257     'interactive_head_name'            => q{Nom},
258     'interactive_head_status'          => q{Statut},
259     'interactive_head_providers'       => q{Fournisseur IDs},
260     'interactive_detail_1'             => q{Origine pour les jeux: },
261     'interactive_detail_2'             => q{Fournir des ensembles: },
262     'interactive_detail_3'             => q{Abonnements: },
263     'interactive_choose_node'          => q{S'il vous plaît choisissez le noeud Ã  déplacer tous les ensembles $1:},
264     'interactive_confirm'              => q{Vous avez choisi de passer ensembles $1 noeud $2 ($3). Est-ce correct [o/n]? },
265     'interactive_drop_nodes'           => q{Voulez-vous laisser tomber aussi les noeuds défaillants de la configuration de slony [o/n]?},
266     'interactive_action'               => q{Meilleur plan d'action est le plus susceptible de faire une $1. Voulez-vous continuer [o/n]?},
267     'interactive_surrender'            => q{Uable pour déterminer le meilleur plan d'action},
268     'interactive_write_script'         => q{Rédaction d'un script Ã  $1 $2 noeud Ã  $3},
269     'interactive_check_nodes'          => q{Vérification de la disponibilité des noeuds de base de donnees...},
270     'interactive_continue'             => q{Voulez-vous continuer [o/n]?},
271     'interactive_preserve'             => q{Préserver les chemins de souscription Ã  suivre le noeud d'origine (ne pas choisir en cas de doute) [o/n]?},
272     'interactive_aliases'              => q{Générer des alias sur la base de sl_node / set commentaires entre parenthèses (ne pas choisir en cas de doute) [o/n]?},
273     'interactive_summary'              => q{Résumé des noeuds Ã  passer Ã  failover:},
274     'interactive_node_info'            => q{Noeud : $1 ($2) $3 (conninfo $4)},
275     'interactive_run_script'           => q{Voulez-vous exécuter ce script maintenant [o/n]?},
276     'interactive_running'              => q{L'exécution du script maintenant. Cela peut prendre un certain temps; s'il vous plaît Ãªtre patient!},
277     'interactive_reason'               => q{S'il vous plaît entrer une brève reson pour cette action: },
278     'interactive_failover_detail_1'    => q{Avant d'aller plus loin s'il vous plaît envisager l'impact d'un failover (basculement) complet:},
279     'interactive_failover_detail_2'    => q{Le noeud vous ne parviennent pas au-dessus de cesse de participer au groupe de façon permanente jusqu'à ce qu'il soit Ã  reconstruire et souscrit},
280     'interactive_failover_detail_3'    => q{Si la panne est temporaire (c.-à-réseau / alimentation / facilement remplaçable matériel connexe) envisager d'attendre dehors},
281     'interactive_failover_detail_4'    => q{Ce type de failover est susceptible d'être plus une décision d'affaires que technique},
282     'info_all_nodes_available'         => q{INFO: Tous les noeuds sont disponibles},
283     'info_req_nodes_available'         => q{INFO: $1 of $2 noeuds sont disponibles. Pas de noeuds indisponibles sont souscrites Ã  l'ancienne origine},
284     'wrn_node_unavailable'             => q{ATTENTION: Noeud $1 disponible},
285     'wrn_req_unavailable'              => q{ATTENTION: Noeud Old origine ($1) est disponible, mais $2 abonnés ne sont pas disponibles},
286     'wrn_not_tested'                   => q{ATTENTION: Script pas testé avec Slony-I v$1},
287     'wrn_failover_issues'              => q{ATTENTION: Slony-I v$1 peut lutter pour basculer correctement avec plusieurs nÅ“uds défaillants (affecte v2.0-2.1)},
288     'note_autofail_fwd_only'           => q{AVIS: Versions antérieures Ã  la 2.2 Slony ne peuvent pas initier le basculement de seulement Ã©choué transmettre fournisseurs},
289     'note_fail_sub_only'               => q{AVIS: Versions antérieures Ã  la 2.2 Slony ne peuvent pas basculer abonnes seuls les noeuds, revenant Ã  failover_offile_subscriber_only = false},
290     'note_multiple_try'                => q{AVIS: Vous ne pouvez pas verrouiller plusieurs ensembles dans des blocs try dans la version $1 de retomber Ã  des jeux simples},
291     'note_reshape_cluster'             => q{AVIS: Vous devez supprimer les abonnés défaillants ou les ramener, puis réessayez Ã  MOVE SET},
292     'err_generic'                      => q{ERREUR: $1},
293     'err_no_database'                  => q{ERREUR: S'il vous plaît spécifier un base de donnees nom},
294     'err_no_cluster'                   => q{ERREUR: S'il vous plaît indiquez un nom de cluster slony},
295     'err_no_host'                      => q{ERREUR: S'il vous plaît spécifier un hôte},
296     'err_no_config'                    => q{ERREUR: Aucune configuration valide n'a Ã©té trouvée},
297     'err_fail_config'                  => q{ERREUR: Impossible de charger la configuration},
298     'err_write_fail'                   => q{ERREUR: Impossible d'écrire dans $1 "$2"},
299     'err_read_fail'                    => q{ERREUR: Impossible de lire $1 "$2"},
300     'err_unlink_fail'                  => q{ERREUR: Impossible de supprimer $1 "$2"},
301     'err_mkdir_fail'                   => q{ERREUR: Impossible de créer $1 répertoire "$2"},
302     'err_execute_fail'                 => q{ERREUR: Impossible d'exécuter $1 "$2"},
303     'err_inactive'                     => q{ERREUR: Noeud $1 n'est pas active (état = $2)},
304     'err_cluster_empty'                => q{ERREUR: Groupe chargé contient pas de noeuds},
305     'err_cluster_offline'              => q{ERREUR: Groupe chargé contient pas de noeuds accessibles},
306     'err_cluster_lone'                 => q{ERRRUE: Groupe chargé ne contient que 1 noeud},
307     'err_not_origin'                   => q{ERREUR: Noeud $1 n'est pas Ã  l'origine de tous les jeux},
308     'err_not_provider'                 => q{ERREUR: Noeud $1 n'est pas un fournisseur de tous les jeux},
309     'err_not_provider_sets'            => q{ERREUR: Noeud $1 ne fournit pas les ensembles nécessaires: le besoin ($2), mais fournit ($3)},
310     'err_no_configuration'             => q{ERREUR: Impossible de lire la configuration pour le noeud $1},
311     'err_must_enter_node_id'           => q{ERREUR: Vous devez entrer un id de noeud},
312     'err_not_a_node_id'                => q{ERREUR: Je n'ai pas connaissance d'un $1 de noeud},
313     'err_same_node'                    => q{ERREUR: Cant déplacer depuis et vers le même noeud},
314     'err_node_offline'                 => q{ERREUR: $1 noeud ($2) n'est pas disponible},
315     'err_incomplete_preamble'          => q{ERREUR: Préambule incomplète},
316     'err_running_slonik'               => q{ERREUR: Ne pouvait pas courir slonik: $1},
317     'err_pgsql_connect'                => q{ERREUR: Impossible de se connecter au serveur postgres},
318     'slonik_output'                    => q{SLONIK: $1},
319     'exit_noaction'                    => q{Quitter, aucune action n'a Ã©té prise},
320     'exit'                             => q{Quitter par $1}
321     }
322 );
323
324
325 # Setup date variables
326 my ($g_year, $g_month, $g_day, $g_hour, $g_min, $g_sec) = (localtime(time))[5,4,3,2,1,0];
327 my $g_date = sprintf ("%02d:%02d:%02d on %02d/%02d/%04d", $g_hour, $g_min, $g_sec, $g_day, $g_month+1, $g_year+1900);
328
329 # Handle command line options
330 Getopt::Long::Configure('no_ignore_case');
331 use vars qw{%opt};
332 die lookupMsg('usage') unless GetOptions(\%opt, 'host|H=s', 'port|p=i', 'dbname|db=s', 'clname|cl=s', 'dbuser|u=s', 'dbpass|P=s', 'cfgfile|f=s', 'infoprint|I', ) and keys %opt and ! @ARGV;
333
334 # Read configuration
335 if (defined($opt{cfgfile})) {
336     unless (getConfig($opt{cfgfile})) {
337         println(lookupMsg('err_no_config'));
338         exit(1);
339     }
340 }
341 else {
342     if (defined($opt{dbname})) {
343         $g_dbname = $opt{dbname};
344     }
345     if (defined($opt{clname})) {
346         $g_clname = $opt{clname};
347     }
348     if (defined($opt{host})) {
349         $g_dbhost = $opt{host};
350     }
351     if (defined($opt{port})) {
352         $g_dbport = $opt{port};
353     }
354     if (defined($opt{dbuser})) {
355         $g_dbuser = $opt{dbuser};
356     }
357     if (defined($opt{dbpass})) {
358         $g_dbpass = $opt{dbpass};
359     }
360 }
361
362 # Fill in any missing values with defaults or display message and die
363 if (!defined($g_dbname)) {
364     println(lookupMsg('err_no_database'));
365     die lookupMsg('usage');
366 }
367 if (!defined($g_clname)) {
368     println(lookupMsg('err_no_cluster'));
369     die lookupMsg('usage');
370 }
371 if (!defined($g_dbhost)) {
372     println(lookupMsg('err_no_host'));
373     die lookupMsg('usage');
374 }
375
376
377 # Build conninfo from supplied datbase name/host/port
378 $g_dbconninfo = "dbname=$g_dbname;host=$g_dbhost;port=$g_dbport";
379
380 if (!defined($opt{infoprint})) {
381     # Check prefix directory and create if not present
382     unless(-e $g_prefix or mkdir $g_prefix) {
383         println(lookupMsg('err_mkdir_fail', 'prefix', $g_prefix));
384         exit(2);
385     }
386
387     if ($g_separate_working) {
388         if ($g_prefix !~ m/\/$/) {
389             $g_prefix .= "/";
390         }
391
392         # Get a uuid for working directory
393         $g_prefix .= getUUID($g_date);
394
395         # Create a working directory and setup log file
396         unless(-e $g_prefix or mkdir $g_prefix) {
397             println(lookupMsg('err_mkdir_fail', 'work', $g_prefix));
398         }
399     }
400 }
401
402 # Set postgres path if provided
403 if (defined($g_slonikpath) && ($g_slonikpath ne "")) {
404     $ENV{PATH} .= ":$g_slonikpath";
405 }
406
407 # Check if autofailover is enabled, if so check configuration and enter autofailover mode
408 if (($g_autofailover) && !defined($opt{infoprint})) {
409
410     # Write out a PID file
411     if (writePID($g_prefix, $g_logfile, $g_log_prefix, $g_pidfile)) {
412         $g_pid_written = true;
413     }
414     else {
415         cleanExit(1, "system");
416     }
417     
418     # Go into endless loop for autofailover
419     autoFailover($g_dbconninfo, $g_clname, $g_dbuser, $g_dbpass, $g_prefix, $g_logfile, $g_log_prefix);
420 }
421
422 # Read slony configuration and output some basic information
423 eval {
424     #local $| = 1;
425     println(lookupMsg('load_cluster', $g_prefix));
426     ($g_node_count, $g_version) = loadCluster($g_dbconninfo, $g_clname, $g_dbuser, $g_dbpass, $g_prefix, $g_logfile, $g_log_prefix);
427 };
428 if ($@) {
429     println(lookupMsg('load_cluster_fail', 'from supplied configuration'));
430     cleanExit(2, "system");
431 }
432
433 if (defined($opt{infoprint})) {
434     println(lookupMsg('load_cluster_success', $g_version, $g_clname, $g_node_count, $g_dbhost, $g_dbport, $g_dbname) . ":");
435     chooseNode("info", undef, undef, undef, 0);
436     exit(0);
437 }
438 else {
439     printlog($g_prefix,$g_logfile,$g_log_prefix,"*"x68 . "\n* ");
440     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('title', $g_script_version));
441     printlogln($g_prefix,$g_logfile,$g_log_prefix,"*"x68);
442 }
443
444 if ($g_node_count <= 0) {
445     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_cluster_empty'));
446     cleanExit(3, "system");
447 }
448 else {
449     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('load_cluster_success', $g_version, $g_clname, $g_node_count, $g_dbhost, $g_dbport, $g_dbname));
450     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('script_settings', $g_lockset_method, $g_failover_method, uc($g_resubscribe_method)));
451 }
452
453 # Output lag information between each node and node configuration was read from
454 if (loadLag($g_dbconninfo, $g_clname, $g_dbuser, $g_dbpass, $g_prefix, $g_logfile, $g_log_prefix) > 0) {
455     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('lag_detail'));
456     foreach (@g_lags) {
457         printlogln($g_prefix,$g_logfile,$g_log_prefix,"\t$_");
458     }
459     printlog($g_prefix,$g_logfile,$g_log_prefix,"\n");
460 }
461
462 # Prompt user to choose nodes to move sets from / to
463 $g_node_from = chooseNode("from", $g_prefix, $g_logfile, $g_log_prefix, 0);
464 if ($g_node_from == 0) {
465     cleanExit(4, "user");
466 }
467 elsif ($g_node_from == -1) {
468     cleanExit(5, "system");
469 }
470
471 $g_node_to = chooseNode("to", $g_prefix, $g_logfile, $g_log_prefix, $g_node_from);
472 if ($g_node_to == 0) {
473     cleanExit(6, "user");
474 }
475 elsif ($g_node_to == -1) {
476     cleanExit(7, "system");
477 }
478 elsif ($g_node_from == $g_node_to) {
479     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_same_node'));
480     cleanExit(8, "system");
481 }
482
483 # Check nodes are available and decide on action to take
484 printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_check_nodes'));
485 ($g_available_node_count, $g_critical_node_count) = checkNodes($g_clname, $g_dbuser, $g_dbpass, $g_node_from, $g_node_to, $g_prefix, $g_logfile, $g_log_prefix);
486
487 if ($g_available_node_count <= 0) {
488     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_cluster_offline'));
489     cleanExit(9, "system");
490 }
491 elsif ($g_critical_node_count == -1) {
492     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_node_offline', 'Target new origin', $g_node_to));
493     cleanExit(10, "system");
494 }
495 elsif ($g_critical_node_count == -2) {
496     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_node_offline', 'Old origin', $g_node_from));
497     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_action', 'FAILOVER'));
498     $g_failover = true;
499 }
500 elsif ($g_critical_node_count == 0) {
501     if ($g_node_count == $g_available_node_count) {
502         printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('info_all_nodes_available'));
503     }
504     else {
505         printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('info_req_nodes_available', $g_available_node_count, $g_node_count));
506     }
507     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_action', 'MOVE SET'));
508 }
509 elsif ($g_critical_node_count > 0) {
510     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('wrn_req_unavailable', $g_node_from, $g_critical_node_count));
511     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('note_reshape_cluster'));
512     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('exit_noaction'));
513     cleanExit(11, "user");
514 }
515 else {
516     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_surrender'));
517     cleanExit(12, "system");
518 }
519 $g_input = <>;
520 chomp($g_input);
521 if ($g_input !~ /^[Y|O]$/i) {
522     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('exit_noaction'));
523     cleanExit(13, "user");
524 }
525
526 if (!$g_use_comment_aliases) {
527     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_aliases'));
528     $g_input = <>;
529     chomp($g_input);
530     if ($g_input =~ /^[Y|O]$/i) {
531         $g_use_comment_aliases = true;
532     }
533 }
534
535 if ($g_failover) {
536     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_summary'));
537
538     foreach (@g_failed) {
539         printlogln($g_prefix,$g_logfile,$g_log_prefix,"\t" . lookupMsg('interactive_node_info',$_->[0],($_->[4] // "unnamed"),(defined($_->[9]) ? "providing sets $_->[9]" : "sole subscriber"), $_->[2])); 
540     }
541
542     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_failover_detail_1'));
543     printlogln($g_prefix,$g_logfile,$g_log_prefix,"\t" . lookupMsg('interactive_failover_detail_2'));
544     printlogln($g_prefix,$g_logfile,$g_log_prefix,"\t" . lookupMsg('interactive_failover_detail_3'));
545     printlogln($g_prefix,$g_logfile,$g_log_prefix,"\t" . lookupMsg('interactive_failover_detail_4'));
546
547     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_drop_nodes'));
548     $g_input = <>;
549     if ($g_input ~~ /^[Y|O]$/i) {
550         $g_drop_failed = true;
551     }
552
553     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_reason'));
554     $g_reason = <>;
555     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_continue'));
556     $g_input = <>;
557     chomp($g_input);
558     if ($g_input !~ /^[Y|O]$/i) {
559         printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('exit_noaction'));
560         cleanExit(14, "user");
561     }
562
563     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_write_script', 'failover from', $g_node_from, $g_node_to));
564     $g_script = writeFailover($g_prefix, $g_dbconninfo, $g_clname, $g_dbuser, $g_dbpass, $g_node_from, $g_node_to, $g_subs_follow_origin, $g_use_comment_aliases, $g_logfile, $g_log_prefix);    
565 }
566 else {
567     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_preserve'));
568     $g_input = <>;
569     chomp($g_input);
570     if ($g_input =~ /^[Y|O]$/i) {
571         $g_subs_follow_origin = true;
572     }
573
574     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_reason'));
575     $g_reason = <>;
576
577     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_write_script', 'move all sets provided by', $g_node_from, $g_node_to));
578     $g_script = writeMoveSet($g_prefix, $g_dbconninfo, $g_clname, $g_dbuser, $g_dbpass, $g_node_from, $g_node_to, $g_subs_follow_origin, $g_use_comment_aliases, $g_logfile, $g_log_prefix);    
579 }
580
581 # Complete and run script if required
582 if (-e $g_script) {
583     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('generated_script', $g_script));
584     printlog($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_run_script', $g_script));
585     $g_input = <>;
586     chomp($g_input);
587     if ($g_input =~ /^[Y|O]$/i) {
588         printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('interactive_running'));
589         unless (runSlonik($g_script, $g_prefix, $g_logfile, $g_log_prefix)) {
590             printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_execute_fail', 'slonik script', $g_script));
591         }
592     }
593     else {
594         printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('exit_noaction'));
595     }
596 }
597 else {
598     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('err_read_fail', 'slonik script', $g_script));
599     cleanExit(15, "system");
600 }
601
602 cleanExit(0, "script completion");
603
604 ###########################################################################################################################################
605
606 sub cleanExit {
607     my $exit_code = shift;
608     my $type = shift;
609
610     printlogln($g_prefix,$g_logfile,$g_log_prefix,lookupMsg('exit', $type));
611
612     if ($g_log_to_db) {    
613         eval {
614            logDB("dbname=$g_logdb_name;host=$g_logdb_host;port=$g_logdb_port", $g_logdb_user, $g_logdb_pass, $exit_code, $g_reason, $g_prefix, $g_logfile, $g_log_prefix, $g_clname, $g_script);
615         };
616     }
617
618     if ($g_pid_written) {
619         removePID($g_prefix, $g_logfile, $g_log_prefix, $g_pidfile);
620     }
621
622     exit($exit_code);
623 }
624
625 sub sigExit {
626     cleanExit(100,'signal');    
627 }
628
629 sub checkNodes {
630     my $clname = shift;
631     my $dbuser = shift;
632     my $dbpass = shift;
633     my $from = shift;
634     my $to = shift;
635     my $prefix = shift;
636     my $logfile = shift;
637     my $log_prefix = shift;
638
639     my $dsn;
640     my $dbh;
641     my $sth;
642     my $query;
643     my $result_count = 0;
644     my $critical_count = 0;
645
646     my @subsets;
647     my @origsets;
648
649     undef @g_failed;
650     undef @g_unresponsive;
651     undef %g_backups;
652
653     foreach (@g_cluster) {
654         if ($_->[0] == $from) {
655             @origsets = split(',', $_->[3]); 
656             last;
657         }
658     }
659
660     foreach (@g_cluster) {
661         if ($g_debug) {
662             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_cluster', $_->[0],($_->[1] // "<NONE>"),$_->[2],($_->[3] // "<NONE>"),$_->[4],($_->[5] // "<NONE>") . "(" . ($_->[8] // "<NONE>") . ")",$_->[6],($_->[7] // "<NONE>"),($_->[9] // "<NONE>") . " (" . ($_->[10] // "<NONE>") . ")"));
663         }
664             
665         $dsn = "DBI:Pg:$_->[2];";
666         eval {
667             $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
668             $query = "SELECT count(*) FROM pg_namespace WHERE nspname = ?";
669             $sth = $dbh->prepare($query);
670             $sth->bind_param(1, "_" . $clname);
671             $sth->execute();
672         
673             $result_count = $result_count+$sth->rows;        
674
675             $sth->finish;
676             $dbh->disconnect();
677
678         };
679         if ($@) {
680             # Critical count will be -1 if the new origin is down, -2 if the old origin is down or positive if subscribers to sets on old origin are down.
681             printlogln($prefix,$logfile,$log_prefix,lookupMsg('wrn_node_unavailable', $_->[0]));
682             if ($g_debug) {
683                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
684             }
685             if ($_->[0] == $to) {
686                 $critical_count = -1;        
687             }
688             elsif ($_->[0] == $from) {
689                 $critical_count = -2;        
690             }
691             else {
692                 foreach my $subprov (split(';', $_->[5])) {
693                     my ($node, $setlist) = (split('->', $subprov)) ;
694                     $node =~ s/n//g;
695                     $setlist =~ s/(\)|\(|s)//g;
696                     @subsets = (split(',', $setlist));
697
698                     if (($critical_count >= 0) && (checkSubscribesAnySets(\@origsets, \@subsets))) {
699                         $critical_count++;    
700                     }
701                 }
702             }
703             # Only push nodes with active subscribers to sets into failed list unless explicitly told to
704             if (($g_fail_subonly) || (defined($_->[9]))) {
705                 push(@g_failed, \@$_);
706                 $g_backups{$_->[0]} = $to;
707             }
708             push(@g_unresponsive, \@$_);
709         }    
710         
711     }
712     return ($result_count, $critical_count);
713 }
714
715 sub loadCluster {
716     my $dbconninfo = shift;
717     my $clname = shift;
718     my $dbuser = shift;
719     my $dbpass = shift;
720     my $prefix = shift;
721     my $logfile = shift;
722     my $log_prefix = shift;
723
724     my $dsn;
725     my $dbh;
726     my $sth;
727     my $query;
728     my $version;
729     my $qw_clname;
730     undef @g_cluster;
731
732     if ($g_debug) {
733         printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_cluster_load', $dbconninfo));
734     }
735
736     $dsn = "DBI:Pg:$dbconninfo;";
737     eval {
738         $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
739         $qw_clname = $dbh->quote_identifier("_" . $clname);
740
741         $query = "SELECT $qw_clname.getModuleVersion()";
742         $sth = $dbh->prepare($query);
743         $sth->execute();
744         ($version) = $sth->fetchrow; 
745         $sth->finish;
746
747         $query = "WITH z AS (
748                 SELECT a.no_id, b.sub_provider AS no_prov,
749                     COALESCE(c.pa_conninfo,(SELECT pa_conninfo FROM $qw_clname.sl_path WHERE pa_server = $qw_clname.getlocalnodeid(?) LIMIT 1)) AS no_conninfo,
750                     array_to_string(array(SELECT set_id FROM $qw_clname.sl_set WHERE set_origin = a.no_id ORDER BY set_id),',') AS origin_sets,
751                     string_agg(CASE WHEN b.sub_receiver = a.no_id AND b.sub_forward AND b.sub_active THEN b.sub_set::text END, ',' ORDER BY b.sub_set) AS prov_sets,
752                     coalesce(trim(regexp_replace(substring(a.no_comment from E'\\\\((.+)\\\\)'), '[^0-9A-Za-z]','_','g')), 'node' || a.no_id) AS no_name,
753                     'n' || b.sub_provider || '->(' || string_agg(CASE WHEN b.sub_receiver = a.no_id THEN 's' || b.sub_set END,',' ORDER BY b.sub_set,',') || ')' AS sub_tree,
754                     coalesce(trim(regexp_replace(substring(d.no_comment from E'\\\\((.+)\\\\)'), '[^0-9A-Za-z]','_','g')), 'node' || b.sub_provider, '')
755                     || '->(' || string_agg(CASE WHEN b.sub_receiver = a.no_id THEN coalesce(trim(regexp_replace(e.set_comment, '[^0-9A-Za-z]', '_', 'g')), 'set' || b.sub_set) END,',' ORDER BY b.sub_set) || ')' AS sub_tree_name,
756                     CASE " . ((substr($version,0,3) >= 2.2) ? "WHEN a.no_failed THEN 'FAILED' " : "") . "WHEN a.no_active THEN 'ACTIVE' ELSE 'INACTIVE' END AS no_status,
757                     array_to_string(array(SELECT DISTINCT sub_set::text FROM $qw_clname.sl_subscribe WHERE sub_provider = a.no_id AND sub_active ORDER BY sub_set),',') AS prov_sets_active,
758                     string_agg(CASE WHEN b.sub_receiver = a.no_id THEN b.sub_set::text END,',' ORDER BY b.sub_set,',') AS sub_sets    
759                 FROM $qw_clname.sl_node a
760                 LEFT OUTER JOIN $qw_clname.sl_subscribe b ON a.no_id = b.sub_receiver
761                 LEFT OUTER JOIN $qw_clname.sl_path c ON c.pa_server = a.no_id AND c.pa_client = $qw_clname.getlocalnodeid(?)
762                 LEFT OUTER JOIN $qw_clname.sl_node d ON b.sub_provider = d.no_id
763                 LEFT OUTER JOIN $qw_clname.sl_set e ON b.sub_set = e.set_id
764                 GROUP BY b.sub_provider, a.no_id, a.no_comment, c.pa_conninfo, d.no_comment, a.no_active
765                 ORDER BY a.no_id
766                 )
767                 SELECT no_id,
768                     nullif(string_agg(no_prov::text, ',' ORDER BY no_prov),'') AS no_provs,
769                     no_conninfo,
770                     nullif(string_agg(origin_sets::text, ',' ORDER BY origin_sets),'') AS origin_sets,
771                     no_name,
772                     nullif(string_agg(sub_tree, ';' ORDER BY sub_tree),'') AS no_sub_tree,
773                     no_status,
774                     nullif(string_agg(prov_sets::text, ',' ORDER BY prov_sets),'') AS prov_sets,
775                     nullif(string_agg(sub_tree_name, ';' ORDER BY sub_tree_name),'') AS no_sub_tree_name,
776                     nullif(string_agg(prov_sets_active::text, ',' ORDER BY prov_sets_active),'') AS prov_sets_active,
777                     nullif(string_agg(sub_sets::text, ',' ORDER BY sub_sets),'') AS no_subs
778                 FROM z GROUP BY no_id, no_conninfo, no_name, no_status";
779         $sth = $dbh->prepare($query);
780
781         $sth->bind_param(1, "_" . $clname);
782         $sth->bind_param(2, "_" . $clname);
783
784         $sth->execute();
785
786         while (my @node = $sth->fetchrow) { 
787             push(@g_cluster,  \@node);
788         }
789
790         $sth->finish;
791
792         $dbh->disconnect();
793     };
794     if ($@) {
795         if ($g_debug) {
796             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
797         }
798         die lookupMsg('err_pgsql_connect');
799     }
800     else {
801         if (substr($version,0,1) < 2) {
802             printlogln($prefix,$logfile,$log_prefix,lookupMsg('wrn_not_tested', $version));
803         }
804         if (($g_use_try_blocks) && ($g_lockset_method eq 'multiple') && (substr($version,0,3) <= 9.9)) {
805             # It's currently not possible to lock multiple sets at a time within a try block (v2.2.2), leave the logic in and set a high version number for now.
806             printlogln($prefix,$logfile,$log_prefix, lookupMsg('note_multiple_try', $version));
807             $g_lockset_method = 'single';
808         }
809         if (substr($version,0,3) >= 2.2) {
810             $g_failover_method = 'new';
811             $g_resubscribe_method = 'resubscribe';
812         }
813         else {
814             unless ($g_silence_notice) {
815                 if ((substr($version,0,3) >= 2.0) && (substr($version,0,3) < 2.2)) {
816                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('wrn_failover_issues', $version));
817                 }
818                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('note_autofail_fwd_only'));
819                 $g_silence_notice = true;
820             }
821             if ($g_fail_subonly) {
822                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('note_fail_sub_only'));
823                 $g_fail_subonly = false;
824             }
825         }
826         
827     }
828
829     return (scalar(@g_cluster), $version);
830 }
831
832 sub loadSets {
833     my $dbconninfo = shift;
834     my $clname = shift;
835     my $nodenumber = shift;
836     my $dbuser = shift;
837     my $dbpass = shift;
838     my $prefix = shift;
839     my $logfile = shift;
840     my $log_prefix = shift;
841     
842     my $dsn;
843     my $dbh;
844     my $sth;
845     my $query;
846     my $qw_clname;
847
848     @g_sets = ();
849     
850     $dsn = "DBI:Pg:$dbconninfo;";
851     eval {
852         $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
853         $qw_clname = $dbh->quote_identifier("_" . $clname);
854         $query = "SELECT set_id, trim(regexp_replace(set_comment,'[^0-9,A-Z,a-z]','_','g')) FROM $qw_clname.sl_set WHERE set_origin = ? ORDER BY set_id;";
855
856         $sth = $dbh->prepare($query);
857         $sth->bind_param(1, $nodenumber);
858
859         $sth->execute();
860
861         while (my @set = $sth->fetchrow) { 
862             push(@g_sets,  \@set);
863         }
864
865         $sth->finish;
866         $dbh->disconnect();
867     };
868     if ($@) {
869         if ($g_debug) {
870             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
871         }
872         die lookupMsg('err_pgsql_connect');
873     }
874
875     return scalar(@g_sets);
876 }
877
878 sub loadLag {
879     my $dbconninfo = shift;
880     my $clname = shift;
881     my $dbuser = shift;
882     my $dbpass = shift;
883     my $prefix = shift;
884     my $logfile = shift;
885     my $log_prefix = shift;
886
887     my $dsn;
888     my $dbh;
889     my $sth;
890     my $query;
891     my $qw_clname;
892
893     @g_lags = ();
894
895     $dsn = "DBI:Pg:$dbconninfo;";
896     eval {
897         $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
898         $qw_clname = $dbh->quote_identifier("_" . $clname);
899         $query = "SELECT a.st_origin || ' (' || coalesce(trim(regexp_replace(substring(b.no_comment from E'\\\\((.+)\\\\)'), '[^0-9A-Za-z]','_', 'g')), 'node' || b.no_id) || ')<->'
900                 || a.st_received || ' (' || coalesce(trim(regexp_replace(substring(c.no_comment from E'\\\\((.+)\\\\)'), '[^0-9A-Za-z]','_', 'g')), 'node' || c.no_id) || ') Events: '
901                 || a.st_lag_num_events || ' Time: ' || a.st_lag_time 
902             FROM $qw_clname.sl_status a
903             INNER JOIN $qw_clname.sl_node b on a.st_origin = b.no_id
904             INNER JOIN $qw_clname.sl_node c on a.st_received = c.no_id";
905
906         $sth = $dbh->prepare($query);
907         $sth->execute();
908
909         while (my $lag = $sth->fetchrow) { 
910             push(@g_lags,  $lag);
911         }
912
913         $sth->finish;
914         $dbh->disconnect();
915     };
916     if ($@) {
917         if ($g_debug) {
918             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
919         }
920         die lookupMsg('err_pgsql_connect');
921     }
922
923     return scalar(@g_lags);
924 }
925
926 sub chooseNode {
927     my $type = shift;
928     my $prefix = shift;
929     my $logfile = shift;
930     my $log_prefix = shift;
931     my $last_choice = shift;
932     my $line;
933     my $choice;
934     my %options;
935     my $ok;
936     my @sets_from;
937     my @sets_to;
938     my $found = false;
939
940     $line = sprintf "%-4s %-14s %-10s %-24s %-s\n", lookupMsg('interactive_head_id'), lookupMsg('interactive_head_name'), lookupMsg('interactive_head_status'), lookupMsg('interactive_head_providers'), lookupMsg('interactive_head_config');
941     printlog($prefix,$logfile,$log_prefix,"$line");
942     $line = sprintf "%-4s %-14s %-10s %-24s %-s\n", "="x(length(lookupMsg('interactive_head_id'))), "="x(length(lookupMsg('interactive_head_name'))), "="x(length(lookupMsg('interactive_head_status'))), "="x(length(lookupMsg('interactive_head_providers'))), "="x(length(lookupMsg('interactive_head_config')));
943     printlog($prefix,$logfile,$log_prefix,"$line");
944
945     foreach (@g_cluster) {
946         $line = sprintf "%-4s %-14s %-10s %-24s %-s\n", $_->[0], $_->[4], $_->[6], ($_->[1] // "<NONE>"), (lookupMsg('interactive_detail_1') . ($_->[3] // "<NONE>"));
947         printlog($prefix,$logfile,$log_prefix,"$line");
948         $line = sprintf "%-55s %-s\n", " ", (lookupMsg('interactive_detail_2') . ($_->[7] // "<NONE>"));
949         printlog($prefix,$logfile,$log_prefix,"$line");
950         $line = sprintf "%-55s %-s\n", " ", (lookupMsg('interactive_detail_3') . ($_->[5] // "<NONE>"));
951         printlogln($prefix,$logfile,$log_prefix,"$line");
952         $options{$_->[0]} = {name => $_->[4], sets => ($_->[3] // ""), status => $_->[6], provider => $_->[7]};
953     }
954     if ($type !~ m/info/i) {
955         printlog($prefix,$logfile,$log_prefix,lookupMsg('interactive_choose_node', $type));
956         $choice = <>;
957         chomp($choice);
958     
959         if(exists($options{$choice})) {
960             if ($options{$choice}->{status} ne "ACTIVE") {
961                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_inactive', $choice, lc($options{$choice}->{status})));
962                 $choice = -1;
963             }
964             elsif (($type =~ m/from/i) && (length(trim($options{$choice}->{sets})) <= 0)) {
965                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_not_origin', $choice));
966                 $choice = -1;
967             }    
968             elsif ($type =~ m/to/i) {
969                 if (length(trim($options{$choice}->{provider})) <= 0) {
970                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_not_provider', $choice));
971                     $choice = -1;
972                 }
973                 else {
974                     foreach my $old_origin (@g_cluster) {
975                         if ($old_origin->[0] == $last_choice) {
976                             @sets_from = split(',', $old_origin->[3]);
977                             @sets_to =  split(',', $options{$choice}->{provider});
978                             if (checkProvidesAllSets(\@sets_from, \@sets_to)) {
979                                 $found = true;
980                             }
981                             else {
982                                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_not_provider_sets',$choice,$old_origin->[3],$options{$choice}->{providers}));
983                                 $choice = -1;
984                             }
985                             last;
986                         }
987                     }
988                     unless ($found) {
989                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_no_configuration', $last_choice));
990                         $choice = -1;
991                     } 
992                 }
993             }    
994             else {
995                 printlog($prefix,$logfile,$log_prefix,lookupMsg('interactive_confirm',$type,$choice,$options{$choice}->{name}));
996                 $ok = <>;
997                 chomp($ok);    
998                 if ($ok !~ /^[Y|O]$/i) {
999                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('exit_noaction'));
1000                     $choice = 0;
1001                 }
1002             }
1003         }
1004         elsif (!length($choice)) {
1005             printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_must_enter_node_id'));
1006             $choice = -1;
1007         }
1008         else {
1009             printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_not_a_node_id', $choice));
1010             $choice = -1;
1011         }
1012     }
1013
1014     return $choice;
1015 }
1016
1017 sub writePreamble {
1018     my $filename = shift;
1019     my $dbconninfo = shift;
1020     my $clname = shift;
1021     my $dbuser = shift;
1022     my $dbpass = shift;
1023     my $sets = shift;
1024     my $aliases = shift;
1025     my $prefix = shift;
1026     my $logfile = shift;
1027     my $log_prefix = shift;
1028     my $comment_all_failed = shift;
1029     my $set_count;
1030     my $line_prefix;
1031     my $success = false;
1032
1033     my ($year, $month, $day, $hour, $min, $sec) = (localtime(time))[5,4,3,2,1,0];
1034     my $date = sprintf ("%02d:%02d:%02d on %02d/%02d/%04d", $hour, $min, $sec, $day, $month+1, $year+1900);
1035
1036     if (open(SLONFILE, ">", $filename)) {    
1037         print SLONFILE ("# Script autogenerated on $date\n\n");
1038         print SLONFILE ("######\n# Preamble (cluster structure)\n######\n\n# Cluster name\n");
1039         if ($aliases) {
1040             print SLONFILE ("DEFINE slony_cluster_name $clname;\n");
1041             print SLONFILE ("CLUSTER NAME = \@slony_cluster_name;\n\n");
1042         }
1043         else {
1044             print SLONFILE ("CLUSTER NAME = $clname;\n\n");
1045         }
1046         foreach (@g_cluster) {
1047             $line_prefix = '';
1048             if (($comment_all_failed) && (exists $g_backups{$_->[0]})) {
1049                 $line_prefix = "# (Node $_->[0] unavailable) ";
1050             }
1051             elsif (!$g_fail_subonly) {
1052                 foreach my $unresponsive (@g_unresponsive) {
1053                     if (($_->[0] == $unresponsive->[0]) && !defined($_->[9]) && ($g_failover_method eq 'new')) {
1054                         $line_prefix = "# (Node $_->[0] unavailable subscriber only) ";
1055                     }
1056                 }
1057             }
1058             print SLONFILE ("# Preamble for node $_->[0] named $_->[4]\n");
1059             if ($aliases) {
1060                 print SLONFILE ($line_prefix . "DEFINE $_->[4] $_->[0];\n");
1061                 print SLONFILE ($line_prefix . "DEFINE $_->[4]_conninfo '$_->[2]';\n");
1062                 print SLONFILE ($line_prefix . "NODE \@$_->[4] ADMIN CONNINFO = \@$_->[4]_conninfo;\n\n");
1063             }
1064             else {
1065                 print SLONFILE ($line_prefix . "NODE $_->[0] ADMIN CONNINFO = '$_->[2]';\n\n");
1066             }
1067             if (($aliases) && ($sets)) {
1068                 $set_count = loadSets($dbconninfo, $clname, $_->[0], $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
1069                 if ($set_count > 0) {
1070                     print SLONFILE ("# Sets provided (currently) by node $_->[0]\n");
1071                     foreach my $set (@g_sets) {
1072                         print SLONFILE ($line_prefix . "DEFINE $set->[1] $set->[0];\n");
1073                     }
1074                     print SLONFILE ("\n");
1075                 }
1076             }
1077         }    
1078         $success = true;
1079     }
1080     else {
1081         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_write_fail', "script", $filename));
1082         $success = false; 
1083     }
1084     return $success;
1085 }
1086
1087 sub writeMoveSet {
1088     my $prefix = shift;
1089     my $dbconninfo = shift;
1090     my $clname = shift;
1091     my $dbuser = shift;
1092     my $dbpass = shift;
1093     my $from = shift;
1094     my $to = shift;
1095     my $subs = shift;
1096     my $aliases = shift;
1097     my $logfile = shift;
1098     my $log_prefix = shift;
1099     my $from_name;
1100     my $to_name;
1101     my $set_count;
1102     my $line_prefix;
1103     my $try_prefix = "";
1104     my ($year, $month, $day, $hour, $min, $sec) = (localtime(time))[5,4,3,2,1,0];
1105     my $filetime = sprintf ("%02d_%02d_%04d_%02d:%02d:%02d", $day, $month+1, $year+1900, $hour, $min, $sec);
1106     my $filename = $prefix . "/" . $clname . "-move_sets_from_" . $from . "_to_" . $to . "_on_" . $filetime . ".scr";
1107
1108     if ($g_use_try_blocks) {
1109         $try_prefix = "\t";
1110     }
1111  
1112     my @subprov_name;
1113     my $subprov_idx;
1114     my $subprov;
1115     my ($node, $setlist);
1116     my ($node_name, $setlist_name);
1117     my @subsets;
1118
1119     unless (writePreamble($filename, $dbconninfo, $clname, $dbuser, $dbpass, true, $aliases, $prefix, $logfile, $log_prefix, true)) {
1120         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_incomplete_preamble'));
1121     }
1122
1123     foreach (@g_cluster) {
1124         if ($_->[0] == $from) {
1125             $from_name = $_->[4];
1126         }
1127         elsif ($_->[0] == $to) {
1128             $to_name = $_->[4];
1129         }
1130     }
1131
1132     if (open(SLONFILE, ">>", $filename)) {
1133
1134         print SLONFILE ("######\n# Actions (changes to cluster structure)\n######\n");
1135         
1136         $set_count = loadSets($dbconninfo, $clname, $from, $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
1137         if ($set_count > 0) {
1138
1139             if ($g_lockset_method ne "single") {
1140                 if ($g_use_try_blocks) {
1141                     print SLONFILE ("TRY {\n");                
1142                 }
1143                 foreach (@g_sets) {
1144                     if ($aliases) {    
1145                         print SLONFILE ($try_prefix . "ECHO 'Locking set $_->[1] ($_->[0])';\n");
1146                         print SLONFILE ($try_prefix . "LOCK SET ( ID = \@$_->[1], ORIGIN = \@$from_name);\n");
1147                     }
1148                     else {
1149                         print SLONFILE ($try_prefix . "ECHO 'Locking set $_->[0]';\n");
1150                         print SLONFILE ($try_prefix . "LOCK SET ( ID = $_->[0], ORIGIN = $from);\n");
1151                     }
1152                 
1153                 }
1154                 print SLONFILE ("\n");
1155                 foreach (@g_sets) {
1156                     if ($aliases) {    
1157                         print SLONFILE ($try_prefix . "ECHO 'Moving set $_->[1] ($_->[0])';\n");
1158                         print SLONFILE ($try_prefix . "MOVE SET ( ID = \@$_->[1], OLD ORIGIN = \@$from_name, NEW ORIGIN = \@$to_name);\n");
1159                     }
1160                     else {
1161                         print SLONFILE ($try_prefix . "ECHO 'Moving set $_->[0]';\n");
1162                         print SLONFILE ($try_prefix . "MOVE SET ( ID = $_->[0], OLD ORIGIN = $from, NEW ORIGIN = $to);\n");
1163                     }
1164                 
1165                 }                
1166                 if ($g_use_try_blocks) {
1167                     print SLONFILE ("}\nON ERROR {\n");
1168                     foreach (@g_sets) {
1169                         if ($aliases) {    
1170                             print SLONFILE ($try_prefix . "ECHO 'Unlocking set $_->[1] ($_->[0])';\n");
1171                             print SLONFILE ($try_prefix . "UNLOCK SET ( ID = \@$_->[1], ORIGIN = \@$from_name);\n");
1172                         }
1173                         else {
1174                             print SLONFILE ($try_prefix . "ECHO 'Unlocking set $_->[0]';\n");
1175                             print SLONFILE ($try_prefix . "UNLOCK SET ( ID = $_->[0], ORIGIN = $from);\n");
1176                         }
1177                     }
1178                     print SLONFILE ("\tEXIT 1;\n}\nON SUCCESS {\n");
1179                 }
1180                 if ($aliases) {    
1181                     print SLONFILE ($try_prefix . "WAIT FOR EVENT (ORIGIN = \@$from_name, CONFIRMED = ALL, WAIT ON = \@$from_name, TIMEOUT = 0);\n");
1182                 }
1183                 else {
1184                     print SLONFILE ($try_prefix . "WAIT FOR EVENT (ORIGIN = $from, CONFIRMED = ALL, WAIT ON = $from, TIMEOUT = 0);\n");
1185                 }
1186                 if ($g_use_try_blocks) {
1187                     print SLONFILE ("}\n");
1188                 }
1189             }
1190             foreach (@g_sets) {
1191                 if ($g_lockset_method eq "single") {
1192                     if ($aliases) {    
1193                         print SLONFILE ("\nECHO 'Moving set $_->[1] ($_->[0])';\n");
1194                         if ($g_use_try_blocks) {
1195                             print SLONFILE ("TRY {\n");
1196                         }
1197                         print SLONFILE ($try_prefix . "LOCK SET ( ID = \@$_->[1], ORIGIN = \@$from_name);\n");
1198                         print SLONFILE ($try_prefix . "MOVE SET ( ID = \@$_->[1], OLD ORIGIN = \@$from_name, NEW ORIGIN = \@$to_name);\n");
1199                         if ($g_use_try_blocks) {
1200                             print SLONFILE ("}\nON ERROR {\n" . $try_prefix . "UNLOCK SET ( ID = \@$_->[1], ORIGIN = \@$from_name);\n" . $try_prefix . "EXIT 1;\n}\n");
1201                         }
1202                         print SLONFILE ("WAIT FOR EVENT (ORIGIN = \@$from_name, CONFIRMED = ALL, WAIT ON = \@$from_name, TIMEOUT = 0);\n");
1203                     }
1204                     else {
1205                         print SLONFILE ("\nECHO 'Moving set $_->[0]';\n");
1206                         if ($g_use_try_blocks) {
1207                             print SLONFILE ("TRY {\n");
1208                         }
1209                         print SLONFILE ($try_prefix . "LOCK SET ( ID = $_->[0], ORIGIN = $from);\n");
1210                         print SLONFILE ($try_prefix . "MOVE SET ( ID = $_->[0], OLD ORIGIN = $from, NEW ORIGIN = $to);\n");
1211                         if ($g_use_try_blocks) {
1212                             print SLONFILE ("}\nON ERROR {\n" . $try_prefix . "UNLOCK SET ( ID = $_->[0], ORIGIN = $from);\n" . $try_prefix . "EXIT 1;\n}\n");
1213                         }
1214                         print SLONFILE ("WAIT FOR EVENT (ORIGIN = $from, CONFIRMED = ALL, WAIT ON = $from, TIMEOUT = 0);\n");
1215                     }
1216                 }    
1217                 if (($subs) && ($g_resubscribe_method eq 'subscribe')) { 
1218     
1219                     foreach my $other_subs (@g_cluster) {
1220                         if (($other_subs->[6] eq "ACTIVE") && ($other_subs->[0] != $from) && ($other_subs->[0] != $to)) {
1221
1222                             if (exists $g_backups{$other_subs->[0]}) {
1223                                 $line_prefix = "# (Node $other_subs->[0] unavailable) ";
1224                             }
1225                             else {
1226                                 $line_prefix = '';
1227                             }
1228
1229                             # mess here needs cleaning up
1230                             @subprov_name = (split(';', $other_subs->[8]));
1231                             $subprov_idx = 0;
1232                             foreach $subprov (split(';', $other_subs->[5])) {
1233                                 ($node, $setlist) = (split('->', $subprov)) ;
1234                                 ($node_name, $setlist_name) = (split('->', $subprov_name[$subprov_idx])) ;
1235                                 $subprov_idx++;
1236                                 $node =~ s/n//g;
1237                                 $setlist =~ s/(\)|\(|s)//g;
1238                                 @subsets = (split(',', $setlist)) ;
1239
1240                                 if ($g_debug) {
1241                                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_resubscribe', $_->[1], $_->[0], $other_subs->[0], $other_subs->[4], $setlist, $setlist_name, $node, $node_name));
1242                                 }    
1243
1244                                 if ($_->[0] ~~ @subsets) {
1245                                     if ($node == $from) {
1246                                         if ($aliases) {
1247                                             print SLONFILE ($line_prefix . 
1248                                                 "ECHO 'Issuing subscribe for set $_->[1] ($_->[0]) provider $to_name ($to) -> " .
1249                                                 "receiver $other_subs->[4] ($other_subs->[0])';\n");
1250                                                    print SLONFILE ($line_prefix . 
1251                                                 "SUBSCRIBE SET ( ID = \@$_->[1], PROVIDER = \@$to_name, " .
1252                                                 "RECEIVER = \@$other_subs->[4], FORWARD = YES);\n");
1253                                         }
1254                                         else {
1255                                             print SLONFILE ($line_prefix . 
1256                                                 "ECHO 'Issuing subscribe for set $_->[1] ($_->[0]) provider $to -> " .
1257                                                 "receiver $other_subs->[0]';\n");
1258                                             print SLONFILE ($line_prefix . "SUBSCRIBE SET ( ID = $_->[0], PROVIDER = $to, " .
1259                                                 "RECEIVER = $other_subs->[0], FORWARD = YES);\n");
1260                                         }
1261                                     }
1262                                     else {
1263                                         if ($aliases) {
1264                                             print SLONFILE ($line_prefix . 
1265                                                 "ECHO 'Issuing subscribe for set $_->[1] ($_->[0]) provider $node_name ($node) -> " . 
1266                                                 "receiver $other_subs->[4] ($other_subs->[0])';\n");
1267                                             print SLONFILE ($line_prefix . "SUBSCRIBE SET ( ID = \@$_->[1], PROVIDER = \@$node_name, " .
1268                                                 "RECEIVER = \@$other_subs->[4], FORWARD = YES);\n");
1269                                             }
1270                                             else {
1271                                                 print SLONFILE ($line_prefix . 
1272                                                 "ECHO 'Issuing subscribe for set $_->[1] ($_->[0]) provider $node -> " .
1273                                                 "receiver $other_subs->[0]';\n");
1274                                                 print SLONFILE ($line_prefix . "SUBSCRIBE SET ( ID = $_->[0], PROVIDER = $node, " .
1275                                                 "RECEIVER = $other_subs->[0], FORWARD = YES);\n");
1276                                         }
1277                                     }
1278                                 }
1279                             }
1280                         }
1281                     }
1282                 }    
1283             }
1284
1285             if (($subs) && ($g_resubscribe_method eq 'resubscribe')) { 
1286
1287                 foreach my $other_subs (@g_cluster) {
1288                     if (($other_subs->[6] eq "ACTIVE") && ($other_subs->[0] != $from) && ($other_subs->[0] != $to)) {
1289                         if (exists $g_backups{$other_subs->[0]}) {
1290                             $line_prefix = "# (Node $other_subs->[0] unavailable) ";
1291                         }
1292                         else {
1293                             $line_prefix = '';
1294                         }
1295
1296                         @subprov_name = (split(';', $other_subs->[8]));
1297                         $subprov_idx = 0;
1298                         foreach $subprov (split(';', $other_subs->[5])) {
1299                             ($node, $setlist) = (split('->', $subprov)) ;
1300                             ($node_name, $setlist_name) = (split('->', $subprov_name[$subprov_idx])) ;
1301                             $subprov_idx++;
1302                             $node =~ s/n//g;
1303     
1304                             print SLONFILE ("\n");
1305                             if ($node == $from) {
1306                                 if ($aliases) {
1307                                     print SLONFILE ($line_prefix .
1308                                         "ECHO 'Issuing resubscribe for provider $to_name ($to) -> receiver $other_subs->[4] ($other_subs->[0])';\n");
1309                                     print SLONFILE ($line_prefix .
1310                                         "RESUBSCRIBE NODE ( ORIGIN = \@$to_name, PROVIDER = \@$to_name, RECEIVER = \@$other_subs->[4]);\n");
1311                                  }
1312                                  else {
1313                                      print SLONFILE ($line_prefix .
1314                                         "ECHO 'Issuing resubscribe for provider $to -> receiver $other_subs->[0]';\n");
1315                                     print SLONFILE ($line_prefix . 
1316                                         "RESUBSCRIBE NODE ( ORIGIN = $to, PROVIDER = $to, RECEIVER = $other_subs->[0] );\n");
1317                                  }            
1318                             }
1319                             else {
1320                                 if ($aliases) {
1321                                     print SLONFILE ($line_prefix .
1322                                         "ECHO 'Issuing resubscribe for provider $node_name ($node) -> receiver $other_subs->[4] ($other_subs->[0])';\n");
1323                                     print SLONFILE ($line_prefix . 
1324                                         "RESUBSCRIBE NODE ( ORIGIN = \@$to_name, PROVIDER = \@$node_name, RECEIVER = \@$other_subs->[4]);\n");
1325                                 }
1326                                 else {
1327                                     print SLONFILE ($line_prefix .
1328                                         "ECHO 'Issuing resubscribe for provider $node -> receiver $other_subs->[0]';\n");
1329                                     print SLONFILE ($line_prefix .
1330                                         "RESUBSCRIBE NODE ( ORIGIN = $to, PROVIDER = $node, RECEIVER = $other_subs->[0]);\n");
1331                                 }
1332                             }
1333                         }
1334                     }
1335                 }
1336             }
1337
1338         }
1339
1340         if ($aliases) {    
1341             print SLONFILE ("\nECHO 'All sets originating from $from_name (id $from) have been moved to $to_name (id $to), ensure you modify any existing slonik scripts to reflect the new origin';\n");
1342         }
1343         else {
1344             print SLONFILE ("\nECHO 'All sets originating from node $from have been moved to node $to, ensure you modify the any existing slonik scripts to reflect the new origin';\n");
1345         }
1346         close (SLONFILE);
1347     }
1348     else {
1349         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_write_fail', "script", $filename));
1350     }
1351     return $filename;
1352 }
1353
1354 sub writeFailover {
1355     my $prefix = shift;
1356     my $dbconninfo = shift;
1357     my $clname = shift;
1358     my $dbuser = shift;
1359     my $dbpass = shift;
1360     my $from = shift;
1361     my $to = shift;
1362     my $subs = shift;
1363     my $aliases = shift;
1364     my $logfile = shift;
1365     my $log_prefix = shift;
1366     my $filename;
1367     my $written;
1368     my $event_node;
1369     my ($year, $month, $day, $hour, $min, $sec) = (localtime(time))[5,4,3,2,1,0];
1370     my $filetime = sprintf ("%02d_%02d_%04d_%02d:%02d:%02d", $day, $month+1, $year+1900, $hour, $min, $sec);
1371     my $sets = false;
1372
1373     my $subprov_idx;
1374     my @subprov_name;
1375     my ($node, $setlist);
1376     my ($node_name, $setlist_name);
1377     my @subsets;
1378     my @subsets_name;
1379     my $set_idx;
1380     my @dropped;
1381
1382     if (defined($from) && defined($to)) {
1383         $filename = $prefix . "/" . $clname . "-failover_from_" . $from . "_to_" . $to . "_on_" . $filetime . ".scr";
1384     }
1385     else {
1386         $filename = $prefix . "/" . $clname . "-autofailover_on_" . $filetime . ".scr";
1387     }
1388
1389     if ($g_failover_method ne 'new') {
1390         # For pre 2.2 failover with multiple nodes, we attempt to resubscribe sets and drop other failed providers;
1391         # This will never work as well as 2.2+ failover behaviour (infact failover may not work as all in 2.0/2.1 with multiple failed nodes)
1392         # We also need to define the sets in the preamble for this.
1393         $sets = true;
1394     }
1395
1396     unless (writePreamble($filename, $dbconninfo, $clname, $dbuser, $dbpass, $sets, $aliases, $prefix, $logfile, $log_prefix, false)) {
1397         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_incomplete_preamble'));
1398     }
1399
1400     if (open(SLONFILE, ">>", $filename)) {
1401
1402         print SLONFILE ("######\n# Actions (changes to cluster structure)\n######\n\n");
1403         if ($g_debug) {
1404             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_failover_method',$g_failover_method));
1405         }
1406
1407         # If we are on pre 2.2 we need to drop failed subscriber nodes first regardless
1408         if ($g_failover_method ne 'new') {
1409             foreach (@g_failed) {
1410                 if (!defined($_->[3])) {
1411                     foreach my $backup (@g_cluster) {
1412                         if ($backup->[0] == $g_backups{$_->[0]}) {  # this backup node candidate is in the list of suitable nodes for {failed node}
1413                             foreach my $subscriber (@g_cluster) {
1414                                 if (defined($subscriber->[1]) && $subscriber->[1] == $_->[0] && $subscriber->[0] != $backup->[0]) {
1415                                     # mess here needs cleaning up
1416                                     @subprov_name = (split(';', $subscriber->[8]));
1417                                     $subprov_idx = 0;
1418                                     foreach my $subprov (split(';', $subscriber->[5])) {
1419                                         ($node, $setlist) = (split('->', $subprov)) ;
1420                                         ($node_name, $setlist_name) = (split('->', $subprov_name[$subprov_idx])) ;
1421                                         $subprov_idx++;
1422                                         $node =~ s/n//g;
1423     
1424                                         if ($node == $_->[0]) {
1425                                             if ($aliases) {
1426                                                 print SLONFILE ("ECHO 'Resubscribing all sets on receiver $subscriber->[4] provided by other failed node $_->[4] to backup node $backup->[4]';\n");
1427                                             }
1428                                             else {
1429                                                 print SLONFILE ("ECHO 'Resubscribing all sets on receiver $subscriber->[0]  provided by other failed node $_->[0] to backup node $backup->[0]';\n");
1430                                             }
1431                                             $setlist =~ s/(\)|\(|s)//g;
1432                                             @subsets = (split(',', $setlist));
1433                                             $setlist_name =~ s/(\)|\()//g;
1434                                             @subsets_name = (split(',', $setlist_name));
1435                                         
1436                                             $set_idx = 0;
1437                                             foreach my $subset (@subsets) {
1438                                                 if ($aliases) {
1439                                                     print SLONFILE ("SUBSCRIBE SET (ID = \@$subsets_name[$set_idx], PROVIDER = \@$backup->[4], RECEIVER = \@$subscriber->[4], FORWARD = YES);\n");
1440                                                     print SLONFILE ("WAIT FOR EVENT (ORIGIN = \@$backup->[4], CONFIRMED = \@$subscriber->[4], WAIT ON = \@$backup->[4]);\n");
1441                                                 }
1442                                                 else {
1443                                                     print SLONFILE ("SUBSCRIBE SET (ID = $subset, PROVIDER = $backup->[0], RECEIVER = $subscriber->[0], FORWARD = YES);\n");
1444                                                     print SLONFILE ("WAIT FOR EVENT (ORIGIN = $backup->[0], CONFIRMED = $subscriber->[0], WAIT ON = $backup->[0]);\n");
1445                                                 }
1446                                                 $set_idx++;
1447                                             }
1448                                             print SLONFILE ("\n");
1449                                         }
1450                                     }
1451     
1452                                     if ($aliases) {
1453                                         print SLONFILE ("ECHO 'Dropping other failed node $_->[4] ($_->[0])';\n");
1454                                          print SLONFILE ("DROP NODE (ID = \@$_->[4], EVENT NODE = \@$backup->[4]);\n\n");
1455                                     }
1456                                     else {
1457                                         print SLONFILE ("ECHO 'Dropping other failed node $_->[0]';\n");
1458                                         print SLONFILE ("DROP NODE (ID = $_->[0], EVENT NODE = $backup->[0]);\n\n");
1459                                     }   
1460                                     push(@dropped, $_->[0]);
1461                                 }
1462                                 else {
1463                                     # The node is failed, but there are no downstream subscribers
1464                                 }
1465                             }
1466                             last;
1467                         }
1468                     }
1469                 }
1470             }
1471         }
1472
1473         foreach (@g_failed) {
1474             if (($g_failover_method eq 'new') || defined($_->[3])) {
1475                 foreach my $backup (@g_cluster) {
1476                     if ($backup->[0] == $g_backups{$_->[0]}) {
1477                         ## Here we have both details of the backup node and the failed node
1478                         if ($aliases) {
1479                             print SLONFILE ("ECHO 'Failing over slony cluster from $_->[4] (id $_->[0]) to $backup->[4] (id $backup->[0])';\n");
1480                         }
1481                         else {
1482                             print SLONFILE ("ECHO 'Failing over slony cluster from node $_->[0] to node $backup->[0]';\n");
1483                         }   
1484                         last;
1485                     }
1486                 }
1487             }
1488         }
1489
1490         print SLONFILE ("FAILOVER (\n\t");
1491         $written = 0;
1492         foreach (@g_failed) {
1493             if (($g_failover_method eq 'new') || defined($_->[3])) {
1494                 foreach my $backup (@g_cluster) {
1495                     if ($backup->[0] == $g_backups{$_->[0]}) {
1496                         ## Here we have both details of the backup node and the failed node
1497                         if ($g_failover_method eq 'new') {
1498                             if( $written != 0 ) {
1499                                 print SLONFILE (",\n\t");
1500                             }
1501                             print SLONFILE ("NODE = (");
1502                         }
1503                         else {
1504                             if( $written != 0 ) {
1505                                 print SLONFILE ("\n);\nFAILOVER (\n\t");
1506                             }
1507                         }
1508                         if ($aliases) {
1509                             print SLONFILE ("ID = \@$_->[4], BACKUP NODE = \@$backup->[4]");
1510                         }
1511                         else {
1512                             print SLONFILE ("ID = $_->[0], BACKUP NODE = $backup->[0]");
1513                         }
1514                         if ($g_failover_method eq 'new') {
1515                             print SLONFILE (")");
1516                         }
1517                         last;
1518                     }
1519                 }
1520                 $written++;
1521             }
1522         }
1523         print SLONFILE ("\n);\n\n");
1524
1525         if ($g_drop_failed) {
1526             if (($g_failover_method eq 'new')  && (scalar(@g_failed) > 1)) {
1527                 foreach (@g_failed) {
1528                     if ($aliases) {
1529                         print SLONFILE ("ECHO 'Dropping failed node $_->[4] ($_->[0])';\n");
1530                     }
1531                     else {
1532                         print SLONFILE ("ECHO 'Dropping failed node $_->[0]';\n");
1533                     }   
1534                 }
1535
1536                 print SLONFILE ("DROP NODE (ID = '");
1537                 undef $event_node;
1538             }
1539             $written = 0;
1540             foreach (@g_failed) {
1541                 foreach my $backup (@g_cluster) {
1542                     if ($backup->[0] == $g_backups{$_->[0]}) {
1543                         if (!defined($event_node)) {
1544                             if ($aliases) {
1545                                 $event_node = $backup->[4];
1546                             }
1547                             else {
1548                                 $event_node = $backup->[0];
1549                             }
1550                         } 
1551                         if (($g_failover_method eq 'new')  && (scalar(@g_failed) > 1)) {
1552                             if( $written != 0 ) {
1553                                 print SLONFILE (",");
1554                             }
1555                             ## Don't bother trying to define array values 
1556                             #if ($aliases) {
1557                             #    print SLONFILE "\@$_->[4]";
1558                             #}
1559                             #else {
1560                                 print SLONFILE $_->[0];
1561                             #}
1562                             $written++;
1563                         }
1564                         elsif (($g_failover_method eq 'new') || defined($_->[3]) || !($_->[0] ~~ @dropped)) {
1565                             if ($aliases) {
1566                                 print SLONFILE ("ECHO 'Dropping failed node $_->[4] ($_->[0])';\n");
1567                                 print SLONFILE ("DROP NODE (ID = \@$_->[4], EVENT NODE = \@$backup->[4]);\n\n");
1568                             }
1569                             else {
1570                                 print SLONFILE ("ECHO 'Dropping failed node $_->[0]';\n");
1571                                 print SLONFILE ("DROP NODE (ID = $_->[0], EVENT NODE = $backup->[0]);\n\n");
1572                             }
1573                         }
1574                         last;
1575                     }
1576                 }   
1577             }
1578             if (($g_failover_method eq 'new')  && (scalar(@g_failed) > 1)) {
1579                 if ($aliases) {
1580                      print SLONFILE ("', EVENT NODE = \@$event_node);\n");
1581                 }
1582                 else {
1583                      print SLONFILE ("', EVENT NODE = $event_node);\n");
1584                 }
1585             }
1586         }
1587
1588     }
1589     else {
1590         printlog($prefix,$logfile,$log_prefix,lookupMsg('err_write_fail', "script", $filename));
1591     }
1592     return $filename;
1593
1594 }
1595
1596 sub lookupMsg {
1597     my $name = shift || '?';
1598     my $line_call;
1599     my $text;
1600
1601     if (exists $message{$g_lang}{$name}) {
1602         $text = $message{$g_lang}{$name};
1603     }
1604     elsif (exists $message{'en'}{$name}) {
1605         $text = $message{'en'}{$name};
1606     }
1607     else {
1608         $line_call = (caller)[2];
1609         $text = qq{Failed to lookup text "$name" at line $line_call};
1610     }
1611
1612     my $x=1;
1613     {
1614         my $val = $_[$x-1];
1615         $val = '?' if ! defined $val;
1616         last unless $text =~ s/\$$x/$val/g;
1617         $x++;
1618         redo;
1619     }
1620     return $text;
1621 }
1622
1623 sub qtrim {
1624     my $string = shift;
1625     $string =~ s/^('|")+//;
1626     $string =~ s/('|")+$//;
1627     return $string;
1628 }
1629
1630 sub trim($) {
1631     my $string = shift;
1632     $string =~ s/^\s+//;
1633     $string =~ s/\s+$//;
1634     return $string;
1635 }
1636
1637 sub println {
1638     print ((@_ ? join($/, @_) : $_), $/);
1639 }
1640
1641 sub printlog {
1642     my $prefix = shift;
1643     my $logfile_name = shift;
1644     my $log_prefix = shift;
1645     my $message = shift;
1646     my $logfile;
1647     my $date;
1648
1649     print $message;
1650
1651     if (defined($logfile_name)) {
1652
1653         # Do we have to do this all the time? Perhaps could check parameters first
1654         if ($logfile_name =~ /^\//i) {
1655             $logfile = strftime($logfile_name, localtime);
1656         }
1657         else {
1658             $logfile = "$prefix/" . strftime($logfile_name, localtime);
1659         }
1660     
1661         if ($log_prefix =~ m/(\%[mt])/) {
1662             my ($year, $month, $day, $hour, $min, $sec) = (localtime(time))[5,4,3,2,1,0];
1663             my ($h_sec, $h_msec) = gettimeofday;
1664             $date = sprintf ("%02d-%02d-%04d %02d:%02d:%02d.%03d", $day, $month+1, $year+1900, $hour, $min, $sec, $h_msec/1000);
1665             $log_prefix =~ s/\%m/$date/g;
1666     
1667             $date = sprintf ("%02d-%02d-%04d %02d:%02d:%02d", $day, $month+1, $year+1900, $hour, $min, $sec);
1668             $log_prefix =~ s/\%t/$date/g;
1669         }
1670         if ($log_prefix =~ m/(\%p)/) {
1671             $log_prefix =~ s/\%p/$g_pid/g;
1672         }
1673
1674         if (open(LOGFILE, ">>", $logfile)) {
1675             print LOGFILE $log_prefix . " " . $message;
1676             close (LOGFILE);
1677         }
1678         else {
1679             println(lookupMsg('err_write_fail', "logfile", $logfile));
1680         }
1681     }
1682 }
1683
1684 sub printlogln {
1685     printlog ($_[0], $_[1], $_[2], $_[3] . $/);
1686 }
1687
1688 sub logDB {
1689     my $dbconninfo = shift;
1690     my $dbuser = shift;
1691     my $dbpass = shift;
1692     my $exit_code = shift;
1693     my $reason = shift;
1694     my $prefix = shift;
1695     my $logfile = shift;
1696     my $log_prefix = shift;
1697     my $clname = shift;
1698     my $script = shift;
1699
1700     my $dsn;
1701     my $dbh;
1702     my $sth;
1703     my $query;
1704
1705     my $results;
1706     my $script_data;
1707
1708     unless($results = (read_file($logfile))) { 
1709         printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_read_fail', "logfile", $logfile));
1710     }
1711
1712     if (defined($script) && (-e $script)) {
1713         unless ($script_data = (read_file($script))) { 
1714             printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_read_fail', "script file", $script));
1715         }
1716     }
1717     else {
1718         $script_data = "No script data was generated.";
1719         $script = "No script generated.";
1720     }
1721
1722     $dsn = "DBI:Pg:$dbconninfo;";
1723     eval {
1724         $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
1725         $query = "INSERT INTO public.failovers (reason, exit_code, results, script, cluster_name)
1726               VALUES (?, ?, ?, ?, ?)";
1727
1728         $sth = $dbh->prepare($query);
1729
1730         $sth->bind_param(1, $reason);
1731         $sth->bind_param(2, $exit_code);
1732         $sth->bind_param(3, $results);
1733         $sth->bind_param(4, $script . ":\n" . $script_data);
1734         $sth->bind_param(5, $clname);
1735
1736         $sth->execute();
1737
1738         $sth->finish;
1739         $dbh->disconnect();
1740     };
1741     if ($@) {
1742         if ($g_debug) {
1743             printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
1744         }
1745         die lookupMsg('err_pgsql_connect');
1746     }
1747
1748     return true;
1749 }
1750
1751 sub getUUID {
1752     my $date_string = shift;
1753     my $g_ug  = new Data::UUID;
1754     my $g_uuid = $g_ug->create_from_name("failover_script", $date_string);
1755     my $g_uuid_str  = $g_ug->to_string($g_uuid);
1756     return $g_uuid_str;
1757 }
1758
1759 sub writePID {
1760     my $prefix = shift;
1761     my $logfile = shift;
1762     my $log_prefix = shift;
1763     my $pidfile_name = shift;
1764     my $pidfile;
1765     my $success = true;
1766  
1767     if ($pidfile_name =~ /^\//i) {
1768         $pidfile = $pidfile_name;
1769     }
1770     else {
1771         $pidfile = "$prefix/" . $pidfile_name;
1772     }
1773     eval {
1774         open (PIDFILE, ">", $pidfile);
1775         print PIDFILE $$;
1776         close (PIDFILE);
1777     };
1778     if ($@) {
1779         if ($g_debug) {
1780             printlogln($prefix,$logfile,$log_prefix, lookupMsg('dbg_generic', $!));       
1781         }
1782         printlogln($prefix,$logfile,$log_prefix, lookupMsg('err_write_fail', "pid file", $pidfile));
1783         $success = false;
1784     }
1785     return $success;
1786 }
1787
1788 sub removePID {
1789     my $prefix = shift;
1790     my $logfile = shift;
1791     my $log_prefix = shift;
1792     my $pidfile_name = shift;
1793     my $pidfile;
1794     my $success = true;
1795
1796     if ($pidfile_name =~ /^\//i) {
1797         $pidfile = $pidfile_name;
1798     }
1799     else {
1800         $pidfile = "$prefix/" . $pidfile_name;
1801     }
1802     eval {
1803         if (-f $pidfile) {
1804             unlink $pidfile;
1805         }
1806         else {
1807             printlogln($prefix,$logfile,$log_prefix, lookupMsg('dbg_generic', 'PID file never existed to be removed'));
1808         } 
1809     };
1810     if ($@) {
1811         if ($g_debug) {
1812             printlogln($prefix,$logfile,$log_prefix, lookupMsg('dbg_generic', $!));
1813         }
1814         printlogln($prefix,$logfile,$log_prefix, lookupMsg('err_unlink_fail', "pid file", $pidfile));
1815         $success = false;
1816     }
1817     return $success;
1818 }
1819
1820 sub checkProvidesAllSets { 
1821     my ($originSets, $providerSets) = @_;
1822     my %test_hash;
1823
1824     undef @test_hash{@$originSets};       # add a hash key for each element of @$originSets
1825     delete @test_hash{@$providerSets};    # remove all keys for elements of @$providerSets
1826
1827     return !%test_hash;              # return false if any keys are left in the hash
1828 }
1829
1830 sub checkSubscribesAnySets {
1831     my ($originSets, $subscriberSets) = @_;
1832     my $before;
1833     my $after;
1834     my %test_hash;
1835
1836     undef @test_hash{@$originSets};       # add a hash key for each element of @$originSets
1837     $before = scalar(keys %test_hash);
1838     delete @test_hash{@$subscriberSets};    # remove all keys for elements of @$subscriberSets
1839     $after = scalar(keys %test_hash);
1840     return ($before != $after);        # return false if no keys were removed from the hash
1841 }
1842
1843 sub getConfig {
1844     my $cfgfile = shift;
1845     my @fields;
1846     my $success = false;
1847     my $value;
1848
1849     if (open(CFGFILE, "<", $cfgfile)) {
1850         foreach (<CFGFILE>) {
1851             chomp $_;
1852             for ($_) {
1853                 s/\r//;
1854                 #s/\#.*//;
1855                 s/#(?=(?:(?:[^']|[^"]*+'){2})*+[^']|[^"]*+\z).*//;
1856             }
1857             if (length(trim($_))) {
1858                 @fields = split('=', $_, 2);
1859                 given(lc($fields[0])) {
1860                     $value = qtrim(trim($fields[1]));
1861                     when(/\blang\b/i) {
1862                         $g_lang = $value;
1863                     }
1864                     when(/\bslony_database_host\b/i) {
1865                         $g_dbhost = $value;
1866                     }
1867                     when(/\bslony_database_port\b/i) {
1868                         $g_dbport = checkInteger($value);
1869                     }
1870                     when(/\bslony_database_name\b/i) {
1871                         $g_dbname = $value;
1872                     }
1873                     when(/\bslony_database_user\b/i) {
1874                         $g_dbuser = $value; 
1875                     }
1876                     when(/\bslony_database_password\b/i) {
1877                         $g_dbpass = $value; 
1878                     }
1879                     when(/\bslony_cluster_name\b/i) {
1880                         $g_clname = $value; 
1881                     }
1882                     when(/\benable_debugging\b/i) {
1883                         $g_debug = checkBoolean($value);
1884                     }
1885                     when(/\bprefix_directory\b/i) {
1886                         $g_prefix = $value;
1887                     }
1888                     when(/\bseparate_working_directory\b/i) {
1889                         $g_separate_working = checkBoolean($value);
1890                     }
1891                     when(/\bpid_filename\b/i) {
1892                         $g_pidfile = $value;
1893                     }
1894                     when(/\bfailover_offline_subscriber_only\b/i) {
1895                         $g_fail_subonly = checkBoolean($value);
1896                     }
1897                     when(/\bdrop_failed_nodes\b/i) {
1898                         $g_drop_failed = checkBoolean($value);
1899                     }
1900                     when(/\blog_line_prefix\b/i) {
1901                         $g_log_prefix = $value;
1902                     }
1903                     when(/\blog_filename\b/i) {
1904                         $g_logfile = $value;
1905                     }
1906                     when(/\blog_to_postgresql\b/i) {
1907                         $g_log_to_db = checkBoolean($value);
1908                     }
1909                     when(/\blog_database_host\b/i) {
1910                         $g_logdb_host = $value;
1911                     }
1912                     when(/\blog_database_port\b/i) {
1913                         $g_logdb_port = checkInteger($value);
1914                     }
1915                     when(/\blog_database_name\b/i) {
1916                         $g_logdb_name = $value;
1917                     }
1918                     when(/\blog_database_user\b/i) {
1919                         $g_logdb_user = $value;
1920                     }
1921                     when(/\blog_database_password\b/i) {
1922                         $g_logdb_pass = $value;
1923                     }
1924                     when(/\benable_try_blocks\b/i) {
1925                         $g_use_try_blocks = checkBoolean($value);
1926                     }
1927                     when(/\bpull_aliases_from_comments\b/i) {
1928                         $g_use_comment_aliases = checkBoolean($value);
1929                     }
1930                     when(/\bslonik_path\b/i) {
1931                         $g_slonikpath = $value;
1932                     }
1933                     when(/\blockset_method\b/i) {
1934                         $g_lockset_method = $value;
1935                     }
1936                     when(/\benable_autofailover\b/i) {
1937                         $g_autofailover = checkBoolean($value);
1938                     }
1939                     when(/\bautofailover_poll_interval\b/i) {
1940                         $g_autofailover_poll_interval = checkInteger($value);
1941                     }
1942                     when(/\bautofailover_node_retry\b/i) {
1943                         $g_autofailover_retry = checkInteger($value);
1944                     }
1945                     when(/\bautofailover_sleep_time\b/i) {
1946                         $g_autofailover_retry_sleep = checkInteger($value);
1947                     }
1948                     when(/\bautofailover_forwarding_providers\b/i) {
1949                         $g_autofailover_provs = checkBoolean($value);
1950                     }
1951                     when(/\bautofailover_config_any_node\b/i) {
1952                         $g_autofailover_config_any = checkBoolean($value);
1953                     }
1954                     when(/\bautofailover_perspective_sleep_time\b/i) {
1955                         $g_autofailover_perspective_sleep = checkInteger($value);
1956                     }
1957                     when(/\bautofailover_majority_only\b/i) {
1958                         $g_autofailover_majority_only = checkBoolean($value);
1959                     }
1960                     when(/\bautofailover_is_quorum\b/i) {
1961                         $g_autofailover_is_quorum  = checkBoolean($value);
1962                     }
1963                 }
1964             }
1965         }
1966         close (CFGFILE);
1967
1968         $success = true;
1969     }
1970     else {
1971         println(lookupMsg('err_fail_config'));
1972     }
1973
1974     return $success;
1975 }
1976
1977 sub checkBoolean {
1978     my $text = shift;
1979     my $value = undef;
1980     if ( grep /^$text$/i, ("y","yes","t","true","on") ) {
1981         $value = true;
1982     }
1983     elsif ( grep /^$text$/i, ("n","no","f","false","off") ) {
1984         $value = false;
1985     }
1986     return $value;
1987 }
1988
1989 sub checkInteger {
1990     my $integer = shift;
1991     my $value = undef;
1992
1993     if (($integer * 1) eq $integer) {
1994         $value = int($integer);
1995     }
1996     return $value;
1997 }
1998
1999
2000 sub runSlonik {
2001     my $script = shift;
2002     my $prefix = shift;
2003     my $logfile = shift;
2004     my $log_prefix = shift;
2005     my $success;
2006
2007     if ($g_debug) {
2008          printlogln($prefix,$logfile,$log_prefix, lookupMsg('dbg_slonik_script', $script));
2009     }
2010     if (open(SLONIKSTATUS, "-|", "slonik $script 2>&1")) {
2011         while (<SLONIKSTATUS>) {
2012             printlogln($prefix,$logfile,$log_prefix,lookupMsg('slonik_output', $_));
2013         }
2014         close(SLONIKSTATUS);
2015         $success = true;
2016     }
2017     else {
2018         printlogln($prefix,$logfile,$log_prefix, lookupMsg('err_running_slonik', $!));
2019         $success = false;
2020     }
2021     return $success;
2022 }
2023
2024 sub autoFailover {
2025     my $dbconninfo = shift;
2026     my $clname = shift;
2027     my $dbuser = shift;
2028     my $dbpass = shift;
2029     my $prefix = shift;
2030     my $logfile = shift;
2031     my $log_prefix = shift;
2032
2033     my $cluster_time;
2034     my $failed;
2035     my $actions;
2036     my $current_retry;
2037     my $cluster_loaded;
2038     my @cluster;
2039     my $node_count;
2040     my $version;
2041
2042     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_init'));
2043     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_init_cnf', ($g_autofailover_config_any ? 'any' : 'specified target')));
2044     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_init_pol', $g_autofailover_poll_interval));
2045     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_init_ret', $g_autofailover_retry, $g_autofailover_retry_sleep));
2046     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_init_set', ($g_autofailover_provs ? 'will' : 'will not')));
2047
2048     while (true) {  
2049         # Probe current cluster configuration every minute 
2050         if (!defined($cluster_time) || (time()-$cluster_time > 60)) {
2051
2052             $cluster_loaded = false;
2053             if (!defined($cluster_time) || !$g_autofailover_config_any) {
2054                 eval {
2055                     ($node_count, $version) = loadCluster($dbconninfo, $clname, $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
2056                     die lookupMsg('err_cluster_empty') if ($node_count == 0);
2057                     @cluster = @g_cluster;
2058                     die lookupMsg('err_cluster_lone') if ($node_count == 1);
2059                     $cluster_loaded = true;
2060                 };
2061                 if ($@) {
2062                     printlogln($prefix,$logfile,$log_prefix, lookupMsg('load_cluster_fail', 'from supplied configuration'));
2063                     if ($g_debug) {
2064                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
2065                     }
2066                 }
2067             }
2068             else {
2069                 foreach (@cluster) {
2070                     if ($_->[6]  eq "ACTIVE") {
2071                         unless ($cluster_loaded) {
2072                             eval {
2073                                 ($node_count, $version) = loadCluster($_->[2], $clname, $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
2074                                 die lookupMsg('err_cluster_empty') if ($node_count == 0);
2075                                 @cluster = @g_cluster;
2076                                 die lookupMsg('err_cluster_lone') if ($node_count == 1);
2077                                 $cluster_loaded = true;
2078                             };
2079                             if ($@) {
2080                                 printlogln($prefix,$logfile,$log_prefix, lookupMsg('load_cluster_fail', 'from node ' . $_->[0] . ': trying next node'));
2081                                 if ($g_debug) {
2082                                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
2083                                 }
2084                             }
2085                         }
2086                     }
2087                 }
2088             }
2089
2090             if ($cluster_loaded) {
2091                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_load_cluster', (!defined($cluster_time) ? "Loaded" : "Reloaded"), $version, $clname, $node_count));
2092                 $cluster_time = time();
2093             }
2094             else {
2095                 printlogln($prefix,$logfile,$log_prefix, lookupMsg('load_cluster_fail', 'from any node'));
2096             }
2097         }
2098
2099         if ($cluster_loaded) {
2100             $current_retry = 0; 
2101             undef $failed;
2102             while(($current_retry <= $g_autofailover_retry) && ((!defined($failed)) || ($failed > 0))) {
2103                 # Check status of cluster
2104                 $failed = checkFailed($clname, $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
2105                 if ($failed == 0) {
2106                     if ($g_debug) {
2107                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_cluster_good'));
2108                     }
2109                     if ($current_retry > 0) {
2110                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('cluster_fixed'));
2111                     }
2112                 }
2113                 $current_retry++;
2114                 if (($failed > 0) && ($current_retry <= $g_autofailover_retry)) {
2115                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('cluster_failed', $failed,$g_autofailover_retry_sleep,$current_retry,$g_autofailover_retry));
2116                     usleep($g_autofailover_retry_sleep * 1000);
2117                 }
2118             }
2119             if ($failed > 0) {
2120                 if ((!$g_autofailover_majority_only || checkSplit($prefix, $logfile, $log_prefix)) && (($g_autofailover_perspective_sleep <= 0) || checkPerspective($clname, $dbuser, $dbpass, $prefix, $logfile, $log_prefix))) {
2121                     $actions = findBackup($clname, $dbuser, $dbpass, $prefix, $logfile, $log_prefix);
2122                     if ($actions > 0) {
2123                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_proceed'));
2124                         foreach my $failed ( keys %g_backups ) {
2125                             printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_detail', $failed, $g_backups{$failed}));
2126                         }
2127                         $g_script = writeFailover($prefix, $dbconninfo, $clname, $dbuser, $dbpass, undef, undef, $g_subs_follow_origin, $g_use_comment_aliases, $logfile, $log_prefix);   
2128                         unless (runSlonik($g_script, $prefix, $logfile, $log_prefix)) {
2129                             printlogln($prefix,$logfile,$log_prefix,lookupMsg('err_execute_fail', 'slonik script', $g_script));
2130                         }
2131                         $cluster_loaded = false;
2132                         #print "SCRIPT: $g_script\n";
2133                         #exit(0);
2134                     }
2135                     else {
2136                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_halt', $failed));
2137                     }
2138                 }
2139             }
2140             usleep($g_autofailover_poll_interval * 1000);
2141         }
2142         else {
2143             sleep(10);
2144         }
2145
2146     }
2147 }
2148
2149 sub checkSplit {
2150     my $prefix = shift;
2151     my $logfile = shift;
2152     my $log_prefix = shift;
2153
2154     my $majority = false; 
2155     my $failed = scalar(@g_unresponsive);
2156     my $survivers = (scalar(@g_cluster) - scalar(@g_unresponsive));
2157
2158     if ($survivers > $failed) {
2159         $majority = true; 
2160         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_split_check', $survivers, ($survivers+$failed)));
2161     }
2162     elsif (($survivers == $failed) && $g_autofailover_is_quorum) {
2163         $majority = true; 
2164         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_split_check', ($survivers . '+quorum'), ($survivers+$failed)));
2165     }
2166     else {
2167         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_split_check_fail', $survivers));
2168     }
2169
2170     return $majority;
2171 }
2172
2173 # Check each nodes perspective of the failure to try to ensure the issue isn't that this script just can't connect to the origin/provider
2174 # The idea here is just to wait for a short period of time and see if the lag time for the nodes has increased by the same amount.
2175 sub checkPerspective {
2176     my $clname = shift;
2177     my $dbuser = shift;
2178     my $dbpass = shift;
2179     my $prefix = shift;
2180     my $logfile = shift;
2181     my $log_prefix = shift;
2182
2183     my $dsn;
2184     my $dbh;
2185     my $sth;
2186     my $query;
2187     my $qw_clname;
2188     my $param_on;
2189     my $agreed = false;
2190     my @unresponsive_ids;
2191     my $lag_idx;
2192     my $lag_confirmed;
2193     my @lag_info1;
2194     my @lag_info2;
2195     my $bad = 0;
2196
2197     foreach (@g_unresponsive) {
2198         push(@unresponsive_ids, $_->[0]);
2199     }
2200     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check', join(", ", @unresponsive_ids), scalar(@g_unresponsive), scalar(@g_cluster)));
2201
2202     foreach (@g_cluster) {
2203         unless ($_->[0] ~~ @unresponsive_ids)  {
2204             $dsn = "DBI:Pg:$_->[2];";
2205             eval {
2206                 $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
2207                 $qw_clname = $dbh->quote_identifier("_" . $clname);
2208
2209                 $query = "SELECT a.st_origin, a.st_received, extract(epoch from a.st_lag_time)::integer
2210                         FROM _test_replication.sl_status a
2211                         INNER JOIN _test_replication.sl_node b on a.st_origin = b.no_id
2212                         INNER JOIN _test_replication.sl_node c on a.st_received = c.no_id
2213                         WHERE a.st_received IN (" . substr('?, ' x scalar(@unresponsive_ids), 0, -2) . ") ORDER BY a.st_origin, a.st_received;";
2214
2215                 $sth = $dbh->prepare($query);
2216
2217                 $param_on = 1; 
2218                 foreach (@unresponsive_ids) {
2219                     $sth->bind_param($param_on, $_);
2220                     $param_on++;
2221                 }
2222                 $sth->execute();
2223
2224                 while (my @node_lag = $sth->fetchrow) { 
2225                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_data', 'Check1', $_->[0], $node_lag[0], $node_lag[1], $node_lag[2]));
2226                     push(@lag_info1, \@node_lag);
2227                 }
2228
2229                 $sth->finish;
2230                 $dbh->disconnect();
2231             };
2232             if ($@) {
2233                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_fail', $_->[0], $@));
2234                 $bad++;
2235             } 
2236         }
2237     }
2238
2239     if ($bad == 0) {
2240         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_sleep', $g_autofailover_perspective_sleep));
2241         usleep($g_autofailover_perspective_sleep * 1000);
2242
2243         foreach (@g_cluster) {
2244             unless ($_->[0] ~~ @unresponsive_ids)  {
2245                 $dsn = "DBI:Pg:$_->[2];";
2246                 eval {
2247                     $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
2248                     $qw_clname = $dbh->quote_identifier("_" . $clname);
2249
2250                     $query = "SELECT a.st_origin, a.st_received, extract(epoch from a.st_lag_time)::integer
2251                             FROM _test_replication.sl_status a
2252                             INNER JOIN _test_replication.sl_node b on a.st_origin = b.no_id
2253                             INNER JOIN _test_replication.sl_node c on a.st_received = c.no_id
2254                             WHERE a.st_received IN (" . substr('?, ' x scalar(@unresponsive_ids), 0, -2) . ") ORDER BY a.st_origin, a.st_received;";
2255
2256                     $sth = $dbh->prepare($query);
2257
2258                     $param_on = 1;
2259                     foreach (@unresponsive_ids) {
2260                         $sth->bind_param($param_on, $_);
2261                         $param_on++;
2262                     }
2263                     $sth->execute();
2264
2265                     while (my @node_lag = $sth->fetchrow) {
2266                         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_data', 'Check2', $_->[0], $node_lag[0], $node_lag[1], $node_lag[2]));
2267                         push(@lag_info2, \@node_lag);
2268                     }
2269
2270                     $sth->finish;
2271                     $dbh->disconnect();
2272                 };
2273                 if ($@) {
2274                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_fail', $_->[0], $@));
2275                     $bad++;
2276                 }
2277             }
2278         }
2279
2280         $lag_idx = 0;
2281         $lag_confirmed = 0;
2282         foreach (@lag_info1) {
2283             if ($g_debug) {
2284                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', ("Node $_->[0] lag between checks on node $_->[1] is " . ($lag_info2[$lag_idx]->[2]-$_->[2]) . " seconds")));
2285             }
2286
2287             if ((($lag_info2[$lag_idx]->[2]-$_->[2])*1000) >= $g_autofailover_perspective_sleep) {
2288                 $lag_confirmed++;
2289             }
2290             $lag_idx++;
2291         }  
2292     }
2293
2294     if ($bad > 0) {
2295         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_unknown'));
2296     }   
2297     elsif ($lag_idx == $lag_confirmed) {
2298         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_true'));
2299         $agreed = true;
2300     } 
2301     else {
2302         printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_pspec_check_false'));
2303     }
2304
2305     return $agreed;
2306 }
2307
2308 sub checkFailed {
2309     my $clname = shift;
2310     my $dbuser = shift;
2311     my $dbpass = shift;
2312     my $prefix = shift;
2313     my $logfile = shift;
2314     my $log_prefix = shift;
2315
2316     my $dsn;
2317     my $dbh;
2318     my $sth;
2319     my $query;
2320     my $result_count = 0;
2321     my $prov_failed = 0;
2322     my $subonly_failed = 0;
2323
2324     undef @g_unresponsive;
2325
2326     foreach (@g_cluster) {
2327         if ($_->[6] eq "ACTIVE") {
2328             if ($g_debug) {
2329                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_autofailover_check',$_->[0], ($_->[4] // "unnamed"),(defined($_->[9]) ? "provider of sets $_->[9]" : "sole subscriber"),$_->[2]));
2330             }
2331
2332             if ($g_debug) {
2333                 if ((defined($_->[3])) || ($g_autofailover_provs && defined($_->[9]))) {
2334                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_autofailover_active_check', 'provider', $_->[0]));
2335                 }
2336                 else {
2337                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_autofailover_active_check', 'subscriber only', $_->[0]));
2338                 }
2339             }
2340
2341             $dsn = "DBI:Pg:$_->[2];";
2342             eval {
2343                 $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
2344                 $query = "SELECT count(*) FROM pg_namespace WHERE nspname = ?";
2345                 $sth = $dbh->prepare($query);
2346                 $sth->bind_param(1, "_" . $clname);
2347                 $sth->execute();
2348
2349                 $result_count = $result_count+$sth->rows;
2350     
2351                 $sth->finish;
2352                 $dbh->disconnect();
2353             };
2354             if ($@) {
2355                 if ($g_debug) {
2356                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
2357                 }
2358                 push(@g_unresponsive, \@$_); 
2359                 if ((defined($_->[3])) || ($g_autofailover_provs && defined($_->[9]))) {
2360                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_unresponsive', $_->[0]));
2361                     unless ($g_failover_method ne 'new' && !defined($_->[3])) {
2362                         $prov_failed++;
2363                     }
2364                 }
2365                 else {
2366                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_unresponsive_subonly', $_->[0]));
2367                     if ($g_fail_subonly) {
2368                         $subonly_failed++;
2369                     }
2370                 }
2371             }
2372         }
2373         else {
2374             if ($g_debug) {
2375                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_autofailover_check',$_->[0], ($_->[4] // "unnamed"), lc($_->[6] // "unknown") .  ' node', $_->[2]));
2376             }
2377         }
2378     }
2379     if ($prov_failed > 0) {
2380         return ($prov_failed+$subonly_failed);
2381     }
2382     else {
2383         return $prov_failed;
2384     }
2385 }
2386
2387 sub findBackup {
2388     my $clname = shift;
2389     my $dbuser = shift;
2390     my $dbpass = shift;
2391     my $prefix = shift;
2392     my $logfile = shift;
2393     my $log_prefix = shift;
2394
2395     my $dsn;
2396     my $dbh;
2397     my $sth;
2398     my $query;
2399     my $qw_clname;
2400     my $result_count = 0;
2401     my $lowest_lag_time;
2402     my $lowest_lag_events;
2403     my $best_node_id;    
2404     my $best_node_is_direct;    
2405     my @sets_from;
2406     my @sets_to;
2407     my %backup_for_set_chosen;
2408
2409     undef %g_backups;
2410     undef @g_failed;
2411
2412     foreach (@g_unresponsive) {
2413         if ($g_fail_subonly || (defined($_->[3])) || ($g_autofailover_provs && defined($_->[9]))) {
2414             printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_promote_find', ($_->[9] // "none"), $_->[0]));
2415
2416             undef $best_node_id;
2417             $lowest_lag_time = (1<<$Config{ivsize}*8-1)-1;
2418             $lowest_lag_events = $lowest_lag_time;
2419
2420             if (defined($_->[9]) && (exists $backup_for_set_chosen{$_->[9]})) {
2421                 $best_node_id = $backup_for_set_chosen{$_->[9]};
2422                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_promote_found', $_->[9], $_->[0]));
2423             }
2424             else {
2425                 foreach my $subscriber (@g_cluster) {
2426                     if ($subscriber->[0] != $_->[0]) {
2427                         if ($g_debug) {
2428                             printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_check_sub',$subscriber->[0]));
2429                         }
2430     
2431                         $dsn = "DBI:Pg:$subscriber->[2]";
2432         
2433                         eval {
2434                             $dbh = DBI->connect($dsn, $dbuser, $dbpass, {RaiseError => 1});
2435                             $qw_clname = $dbh->quote_identifier("_" . $clname);
2436
2437                             $query = "SELECT extract(epoch from a.st_lag_time), a.st_lag_num_events, (a.st_received = ?) AS direct
2438                                 FROM $qw_clname.sl_status a
2439                                 INNER JOIN $qw_clname.sl_subscribe b ON b.sub_provider = a.st_received AND b.sub_receiver = a.st_origin
2440                                 WHERE b.sub_active 
2441                                 GROUP BY a.st_lag_time, a.st_lag_num_events, a.st_received;";
2442
2443                             $sth = $dbh->prepare($query);
2444                             $sth->bind_param(1, $_->[0]);
2445                             $sth->execute();
2446
2447                             while (my @subinfo = $sth->fetchrow) {
2448
2449                                 undef @sets_from;
2450                                 if (defined($_->[9])) {
2451                                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_node_detail', $subscriber->[0], ($subinfo[2]?"directly":"indirectly"), (defined($_->[3])?"origin":"provider"), $_->[0], $subscriber->[7], $subinfo[0], $subinfo[1]));
2452                                     @sets_from = split(',',$_->[9]);
2453                                     @sets_to = split(',',$subscriber->[7]);
2454                                 }
2455                                 elsif ($g_fail_subonly) {
2456                                     # Subscriber only node will have no active sets forwarding sets to check
2457                                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_node_detail_subonly', $subscriber->[0], ($subinfo[2]?"directly":"indirectly"), (defined($_->[3])?"origin":"provider"), $_->[0], $subinfo[0], $subinfo[1]));
2458                                     @sets_from = (0);
2459                                     @sets_to = (0);
2460                                 }
2461
2462                                 if ((checkProvidesAllSets(\@sets_from, \@sets_to)) && (($subinfo[0] < $lowest_lag_time && ($subinfo[2] || !defined($best_node_id))) || (!$best_node_is_direct && $subinfo[2]))) {
2463                                     printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_promote_best', $subscriber->[0], $subinfo[0], $subinfo[1]));
2464                                     $best_node_id = $subscriber->[0];
2465                                     $lowest_lag_time = $subinfo[0];
2466                                     $lowest_lag_events = $subinfo[1];
2467                                     $best_node_is_direct = $subinfo[2];
2468                                 }
2469                             }
2470                         };
2471                         if ($@) {
2472                             printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_check_sub_fail', $subscriber->[0]));
2473                             if ($g_debug) {
2474                                 printlogln($prefix,$logfile,$log_prefix,lookupMsg('dbg_generic', $@));
2475                             }
2476                         }
2477                     }
2478                 }
2479             }
2480             if (defined($best_node_id)) { 
2481                 push(@g_failed, \@$_);
2482                 $g_backups{$_->[0]} = $best_node_id;
2483                 if (defined($_->[9]) && !(exists $g_backups{$_->[9]})) {
2484                     $backup_for_set_chosen{$_->[9]} = $best_node_id;
2485                 }
2486             }
2487             else {
2488                 printlog($prefix,$logfile,$log_prefix,lookupMsg('autofailover_promote_fail')); 
2489             }
2490         }
2491         else {
2492             printlogln($prefix,$logfile,$log_prefix,lookupMsg('autofailover_promote_skip', $_->[0]));
2493         }
2494     }
2495     return keys(%g_backups);
2496 }