CVS User Account cvsuser
Thu Sep 30 18:37:34 PDT 2004
Log Message:
-----------
Add the "altperl" tools into stable branch

Tags:
----
REL_1_0_STABLE

Added Files:
-----------
    slony1-engine/tools/altperl:
        Makefile (r1.1.2.1)
        README (r1.6.2.1)
        ToDo (r1.2.2.1)
        build_env.pl (r1.5.2.1)
        create_set.pl (r1.6.2.1)
        drop_node.pl (r1.4.2.1)
        drop_set.pl (r1.4.2.1)
        failover.pl (r1.4.2.1)
        init_cluster.pl (r1.4.2.1)
        merge_sets.pl (r1.4.2.1)
        move_set.pl (r1.3.2.1)
        replication_test.pl (r1.1.2.1)
        reset_cluster.pl (r1.4.2.1)
        restart_node.pl (r1.3.2.1)
        restart_nodes.pl (r1.2.2.1)
        show_configuration.pl (r1.1.2.1)
        show_nodes.pl (r1.1.2.1)
        slon-tools.pm (r1.11.2.1)
        slon.env (r1.5.2.1)
        slon_kill.pl (r1.4.2.1)
        slon_pushsql.pl (r1.5.2.1)
        slon_start.pl (r1.5.2.1)
        slon_watchdog.pl (r1.3.2.1)
        slon_watchdog2.pl (r1.2.2.1)
        subscribe_set.pl (r1.4.2.1)
        uninstall_nodes.pl (r1.2.2.1)
        unsubscribe_set.pl (r1.3.2.1)
        update_nodes.pl (r1.2.2.1)

-------------- next part --------------
--- /dev/null
+++ tools/altperl/merge_sets.pl
@@ -0,0 +1,45 @@
+#!perl # -*- perl -*-
+# $Id: merge_sets.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($node, $set1, $set2) = @ARGV;
+if ($node =~ /^node(\d+)$/) {
+  # Set name is in proper form
+  $node = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./merge_sets.pl nodeN setOLD setNEW\n";
+}
+
+if ($set1 =~ /^set(\d+)$/) {
+  $set1 = $1;
+} else {
+  print "Valid set names are set1, set2, ...\n\n";
+  die "Usage: ./merge_sets.pl nodeN setOLD setNEW\n";
+}
+if ($set2 =~ /^set(\d+)$/) {
+  $set2 = $1;
+} else {
+  print "Valid set names are set1, set2, ...\n\n";
+  die "Usage: ./merge_sets.pl nodeN setOLD setNEW\n";
+}
+
+open(SLONIK, ">/tmp/slonik.$$");
+print SLONIK genheader();
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK qq[
+try {
+      merge set (id = $set1, add id = $set2, origin = $node);
+} on error {
+      echo 'Failure to merge sets $set1 and $set2 with origin $node';
+      exit 1;
+}
+echo 'Replication set $set2 merged in with $set1 on origin $node';
+];
+
+close SLONIK;
+run_slonik_script("/tmp/slonik.$$");
--- /dev/null
+++ tools/altperl/create_set.pl
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+# $Id: create_set.pl,v 1.6.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+my ($set) = @ARGV;
+if ($set =~ /^set(\d+)$/) {
+  $set = $1;
+} else {
+  print "Need set identifier\n";
+  die "create_set.pl setN\n";
+}
+
+$OUTPUTFILE="/tmp/add_tables.$$";
+
+open (OUTFILE, ">$OUTPUTFILE");
+print OUTFILE genheader();
+
+foreach my $table (@SERIALTABLES) {
+  $table = ensure_namespace($table);
+  print OUTFILE "
+		echo '  Adding unique key to table $table...';
+		table add key (
+		    node id=1,
+		    full qualified name='$table'
+		);
+";
+}
+close OUTFILE;
+run_slonik_script($OUTPUTFILE);
+
+open (OUTFILE, ">$OUTPUTFILE");
+print OUTFILE genheader();
+
+print OUTFILE "
+try {
+      create set (id = $set, origin = 1, comment = 'Set $set for $SETNAME');
+} on error {
+      echo 'Could not create subscription set $set for $SETNAME!';
+      exit -1;
+}
+";
+
+close OUTFILE;
+run_slonik_script($OUTPUTFILE);
+
+open (OUTFILE, ">$OUTPUTFILE");
+print OUTFILE genheader();
+print OUTFILE "
+	echo 'Subscription set $set created';
+	echo 'Adding tables to the subscription set';
+
+";
+
+if ($TABLE_ID < 1) {
+  $TABLE_ID = 1;
+}
+foreach my $table (@SERIALTABLES) {
+  $table = ensure_namespace($table);
+  print OUTFILE "
+		set add table (set id = $set, origin = 1, id = $TABLE_ID, full qualified name = '$table', comment = 'Table $table without primary key', key=serial);
+                echo 'Add unkeyed table $table';
+"; 
+  $TABLE_ID++;
+}
+
+foreach my $table (@PKEYEDTABLES) {
+  $table = ensure_namespace($table);
+  print OUTFILE "
+		set add table (set id = $set, origin = 1, id = $TABLE_ID, full qualified name = '$table', comment = 'Table $table with primary key');
+                echo 'Add primary keyed table $table';
+";
+  $TABLE_ID++;
+}
+
+foreach my $table (keys %KEYEDTABLES) {
+  my $key = $KEYEDTABLES{$table};
+  $table = ensure_namespace($table);
+  print OUTFILE "
+		set add table (set id = $set, origin = 1, id = $TABLE_ID, full qualified name = '$table', key='$key', comment = 'Table $table with candidate primary key $key');
+                echo 'Add candidate primary keyed table $table';
+";
+  $TABLE_ID++;
+}
+
+close OUTFILE;
+run_slonik_script($OUTPUTFILE);
+
+open (OUTFILE, ">$OUTPUTFILE");
+print OUTFILE genheader();
+# Finish subscription set...
+print OUTFILE "
+                echo 'Adding sequences to the subscription set';
+";
+
+$SEQID=1;
+foreach my $seq (@SEQUENCES) {
+  $seq = ensure_namespace($seq);
+  print OUTFILE "
+                set add sequence (set id = $set, origin = 1, id = $SEQID, full qualified name = '$seq', comment = 'Sequence $seq');
+                echo 'Add sequence $seq';
+";
+  $SEQID++;
+}
+print OUTFILE "
+        echo 'All tables added';
+";
+
+run_slonik_script($OUTPUTFILE);
+
+### If object hasn't a namespace specified, assume it's in "public", and make it so...
+sub ensure_namespace {
+  my ($object) = @_;
+    if ($object =~ /^(.*\..*)$/) {
+    # Table has a namespace specified
+  } else {
+    $object = "public.$object";
+  }
+  return $object;
+}
+
--- /dev/null
+++ tools/altperl/subscribe_set.pl
@@ -0,0 +1,53 @@
+#!perl # -*- perl -*-
+# $Id: subscribe_set.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+my ($set, $node) = @ARGV;
+if ($node =~ /^node(\d+)$/) {
+  $node = $1;
+} else {
+  print "Need to specify node!\n";
+  die "subscribe_set setM nodeN\n";
+}
+
+if ($set =~ /^set(\d+)$/) {
+  $set = $1;
+} else {
+  print "Need to specify set!\n";
+  die "subscribe_set setM nodeN\n";
+}
+
+$FILE="/tmp/slonik-subscribe.$$";
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+print SLONIK "try {\n";
+
+if ($DSN[$node]) {
+  my $parent = 1;
+  my $forward;
+  if ($PARENT[$node]) {
+    $parent = $PARENT[$node];
+  }
+  if ($NOFORWARD[$node] eq "yes") {
+    $forward = "no";
+  } else {
+    $forward = "yes";
+  }
+  print SLONIK "   subscribe set (id = $set, provider = $parent, receiver = $node, forward = $forward);\n";
+} else {
+  die "Node $node not found\n";
+}
+
+print SLONIK "}\n";
+print SLONIK qq{
+        on error {
+                exit 1;
+        }
+        echo 'Subscribed nodes to set $set';
+};
+
+close SLONIK;
+run_slonik_script($FILE);
--- /dev/null
+++ tools/altperl/init_cluster.pl
@@ -0,0 +1,210 @@
+#!perl # -*- perl -*-
+# $Id: init_cluster.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+my @COST;
+my @PATH;
+
+require 'slon-tools.pm';
+require 'slon.env';
+my $FILE="/tmp/init-cluster.$$";
+open(SLONIK, ">$FILE");
+
+print SLONIK genheader();
+
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK "
+   init cluster (id = 1, comment = 'Node $node - $dbname\@$dbhost');
+";
+close SLONIK;
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+foreach my $node (@NODES) {
+  if ($node > 1) {		# skip the first one; it's already initialized!
+    my ($dbname, $dbhost) = ($DBNAME[$node], $HOST[$node]);
+    print SLONIK "   store node (id = $node, comment = 'Node $node - $dbname\@$dbhost');\n";
+  }
+}
+
+print SLONIK "echo 'Set up replication nodes';
+";
+close SLONIK;
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+my @VIA ;
+generate_listen_paths();
+report_on_paths();
+print SLONIK qq[
+echo 'Next: configure paths for each node/origin';
+];
+foreach my $nodea (@NODES) {
+  my $dsna = $DSN[$nodea];
+  foreach my $nodeb (@NODES) {
+    if ($nodea != $nodeb) {
+      my $dsnb = $DSN[$nodeb];
+      my $providerba = $VIA[$nodea][$nodeb];
+      my $providerab = $VIA[$nodeb][$nodea];
+      if (!$printed[$nodea][$nodeb]) {
+	print SLONIK "      store path (server = $nodea, client = $nodeb, conninfo = '$dsna');\n";
+	$printed[$nodea][$nodeb] = "done";
+      }
+      if (!$printed[$nodeb][$nodea]) {
+	print SLONIK "      store path (server = $nodeb, client = $nodea, conninfo = '$dsnb');\n";
+	$printed[$nodeb][$nodea] = "done";
+      }
+      print SLONIK "echo 'configured path between $nodea and $nodeb';\n";
+    }
+  }
+}
+
+close SLONIK;
+
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+foreach my $origin (@NODES) {
+  my $dsna = $DSN[$origin];
+  foreach my $receiver (@NODES) {
+    if ($origin != $receiver) {
+      my $provider = $VIA[$origin][$receiver];
+      print SLONIK "      store listen (origin = $origin, receiver = $receiver, provider = $provider);\n";
+    }
+  }
+}
+
+print SLONIK qq[
+        echo 'Replication nodes prepared';
+        echo 'Please start a slon replication daemon for each node';
+];
+
+close SLONIK;
+run_slonik_script($FILE);
+
+sub generate_listen_paths {
+  my @COST;
+  my @PATH;
+
+  my $infinity = 10000000;	# Initial costs are all infinite
+  foreach my $node1 (@NODES) {
+    foreach my $node2 (@NODES) {
+      $COST[$node1][$node2] = $infinity;
+    }
+  }
+
+  # Initialize paths between parents and children, and based on them,
+  # generate initial seeding of listener paths, @VIA
+
+  foreach my $node1 (@NODES) {
+    $COST[$node1][$node1] = 0;
+    $VIA[$node1][$node1] = 0;
+    foreach my $node2 (@NODES) {
+      if ($node2 != $node1) {
+	if ($PARENT[$node1] == $node2) {
+	  $PATH[$node1][$node2] = 1;
+	  $PATH[$node2][$node1] = 1;
+	  # Set up a cost 1 path between them
+	  # Parent to child
+	  $COST[$node1][$node2] = 1;
+	  $VIA[$node1][$node2] = $node1;
+
+	  # Child to parent
+	  $COST[$node2][$node1] = 1;
+	  $VIA[$node2][$node1] = $node2;
+	}
+      }
+    }
+  }
+
+  # Now, update the listener paths...
+  # 4 level nested iteration:
+  # 1 while not done, do
+  #   2 for each node, node1
+  #     3 for each node, node2, where node2 <> node1, where we don't
+  #           yet have a listener path
+  #       4 for each node node3 (<> node1 or node2),
+  #          consider introducing the listener path:
+  #                 node1 to node2 then node2 to node3
+  # In concept, it's an O(n^4) algorithm; since the number of nodes, n,
+  # is not likely to get particularly large, it's not worth tuning
+  # further.
+  $didwork = "yes";
+  while ($didwork eq "yes") {
+    $didwork = "no";
+    foreach my $node1 (@NODES) {
+      foreach my $node3 (@NODES) {
+	if (($VIA[$node3][$node1] == 0) && ($node3 != $node1)) {
+	  foreach my $node2 (@NODES) {
+	    if ($PATH[$node1][$node2] && ($VIA[$node2][$node3] != 0) && ($node2 != $node3) && ($node2 != $node1)) {
+	      # Consider introducing a path from n1 to n2 then n2 to n3
+	      # as a cheaper alternative to going direct from n1 to n3
+	      my $oldcost = $COST[$node3][$node1];
+	      my $newcost = $COST[$node1][$node2] + $COST[$node2][$node3];
+	      if ($newcost < $oldcost) {
+		$didwork = "yes";
+				# So we go via node 2
+		$VIA[$node3][$node1] = $node2;
+		$COST[$node3][$node1] = $newcost;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+sub report_on_paths {
+  print "cost\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d|", $node1;
+    foreach my $node2 (@NODES) {
+      if ($COST[$node2][$node1] == $infinity) {
+	printf "inf  ";
+      } else {
+	printf "%4d ", $COST[$node2][$node1];
+      }
+      print "\n";
+    }
+  }
+  print "\n\n";
+  print "VIA\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d", $node1;
+    foreach my $node2 (@NODES) {
+      printf "%4d ", $VIA[$node2][$node1];
+    }
+    print "\n";
+  }
+
+  print "PATHS\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d", $node1;
+    foreach my $node2 (@NODES) {
+      printf "%4d ", $PATH[$node2][$node1];
+    }
+    print "\n";
+  }
+}
--- /dev/null
+++ tools/altperl/slon.env
@@ -0,0 +1,68 @@
+#!perl # -*- perl -*-
+# $Id: slon.env,v 1.5.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+if ($ENV{"SLONYNODES"}) {
+  require $ENV{"SLONYNODES"};
+} else {
+  # Define environment locally...
+  $SETNAME=flex2test;
+  $LOGDIR='/opt/logs/slon';
+  $SLON_BIN_PATH='/opt/OXRS/dbs/pgsql74/bin';
+  #$APACHE_ROTATOR="/opt/OXRS/apache/rotatelogs";   # optional path to Apache rotatelog tool
+
+  add_node(host => 'marge', dbname=>'transtest', port=>5532,
+	   user=>'postgres', password=>'postgres', node=>1);
+
+  add_node(host => 'marge', dbname=>'transreplica', port=>5533, 
+	   user=>'postgres', password=>'postgres', node=>2, parent=>1);
+
+  add_node(host => 'marge', dbname=>'transreplica', port=>5534, user=>'postgres',
+	   password=>'postgres', node=>3, parent=>1);
+
+  # add_node(host => 'marge', dbname=>'flexnodeb', port=>5532,user=>'postgres',
+  #  	 password=>'postgres', node=>4, parent=>3);
+
+  # add_node(host => 'marge', dbname=>'flexnodec', port=>5532,user=>'postgres',
+  # 	 password=>'postgres', node=>5, parent=>4);
+
+  # add_node(host => 'marge', dbname=>'flexnoded', port=>5532,user=>'postgres',
+  #  	 password=>'postgres', node=>6, parent=>3);
+  # add_node(host => 'marge', dbname=>'flexnodee', port=>5532,user=>'postgres',
+  #  	 password=>'postgres', node=>7, parent=>6, noforward=>'no');
+}
+
+if ($ENV{"SLONYSET"}) {
+  require $ENV{"SLONYSET"};
+} else {
+
+  # Table Numbering - controlled here...
+  $TABLE_ID=1;
+  # These are the tables that already have primary keys, that therefore do
+  # not need for Slony-I to add sequences/indices
+  @PKEYEDTABLES=(
+		); 
+ 
+  # These are tables with candidate primary keys; we assume Slony
+  # isn't smart enough (yet) to discover the key.
+
+  %KEYEDTABLES=(		
+		table1 => 'index_on_table1',
+		table2 => 'index_on_table2'
+	       );
+
+  # Here are the tables to be replicated that do NOT have unique
+  # keys, to which Slony-I will have to add a key field
+  @SERIALTABLES=(
+		);
+
+  # These are the applications' sequences that are to be
+  # replicated
+  @SEQUENCES=(
+	      "seq1",
+	      "seq2",
+	      "seq3"
+	     );	
+
+}
--- /dev/null
+++ tools/altperl/drop_set.pl
@@ -0,0 +1,29 @@
+#!perl # -*- perl -*-
+# $Id: drop_set.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+my ($set) = @ARGV;
+if ($set =~ /^set(\d+)$/) {
+  $set = $1;
+} else {
+  print "Need set identifier\n";
+  die "drop_set.pl setN\n";
+}
+$OUTFILE="/tmp/dropset.$$";
+open(SLONIK, ">>$OUTFILE");
+
+print SLONIK genheader();
+
+print SLONIK qq{
+try {
+      drop set (id = $set, origin=1);
+} on error {
+      exit 1;
+}
+echo 'Dropped set $set';
+};
+close SLONIK;
+run_slonik_script($OUTFILE);
--- /dev/null
+++ tools/altperl/slon_start.pl
@@ -0,0 +1,44 @@
+#!perl # -*- perl -*-
+# $Id: slon_start.pl,v 1.5.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+#start the slon daemon
+require 'slon-tools.pm';
+require 'slon.env';
+$SLEEPTIME=30;   # number of seconds for watchdog to sleep
+
+$node =$ARGV[0];
+
+if ( scalar(@ARGV) < 1 ) {
+  die "Usage: ./slon_start [node]\n";
+}
+
+if ($node =~ /^node(\d+)$/) {
+  # Node name is in proper form
+  $nodenum = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./slon_start [node]\n";
+}
+
+$pid = get_pid($node);
+
+if ($pid) {
+  die "Slon is already running for set $SETNAME!\n";
+}
+
+my $dsn = $DSN[$nodenum];
+my $dbname=$DBNAME[$nodenum];
+start_slon($nodenum);
+
+$pid = get_pid($node);
+
+if (!($pid)) {
+  print "Slon failed to start for cluster $SETNAME, node $node\n";
+} else {
+  print "Slon successfully started for cluster $SETNAME, node $node\n";
+  print "PID [$pid]\n";
+  print "Start the watchdog process as well...\n";
+  system "perl slon_watchdog.pl $node $SLEEPTIME &";
+}
--- /dev/null
+++ tools/altperl/reset_cluster.pl
@@ -0,0 +1,48 @@
+#!perl # -*- perl -*-
+# $Id: reset_cluster.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+open(SLONIK, ">/tmp/slonik.$$");
+
+print SLONIK genheader();
+
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK "
+try {
+";
+
+foreach my $node (@NODES) {
+    if ($node > 1) {
+	my ($dbname, $dbhost) = ($DBNAME[$node], $HOST[$node]);
+	print SLONIK "     store node (id = $node, comment = 'Node $dbname@$dbhost');\n";
+    }
+}
+
+foreach my $nodea (@NODES) {
+    my $dsna = $DSN[$nodea];
+    foreach my $nodeb (@NODES) {
+	if ($nodea != $nodeb) {
+	    my $dsnb = $DSN[$nodeb];
+	    print SLONIK "      store path (server = $nodea, client = $nodeb, conninfo = '$dsna');\n";
+	    print SLONIK "      store path (server = $nodeb, client = $nodea, conninfo = '$dsnb');\n";
+	    print SLONIK "      store listen (origin = $nodea, receiver = $nodeb);\n";
+	    print SLONIK "      store listen (origin = $nodeb, receiver = $nodea);\n";
+	}
+    }
+}
+
+print SLONIK qq[
+} on error {
+  echo 'Remapping of cluster failed...';
+  exit 1;
+}
+echo 'Replication nodes prepared';
+echo 'Please start a slon replication daemon for each node';
+];
+
+close SLONIK;
+run_slonik_script("/tmp/slonik.$$");
--- /dev/null
+++ tools/altperl/unsubscribe_set.pl
@@ -0,0 +1,37 @@
+#!perl # -*- perl -*-
+# $Id: unsubscribe_set.pl,v 1.3.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($set, $node) = @ARGV;
+if ($node =~ /^node(\d+)$/) {
+  $node = $1;
+} else {
+  print "Need to specify node!\n";
+  die "unsubscribe_set setM nodeN\n";
+}
+
+if ($set =~ /^set(\d+)$/) {
+  $set = $1;
+} else {
+  print "Need to specify set!\n";
+  die "unsubscribe_set setM nodeN\n";
+}
+
+open(SLONIK, ">/tmp/slonik-unsubscribe.$$");
+print SLONIK genheader();
+print SLONIK qq{
+        try {
+                unsubscribe set (id = $set, receiver = $node);
+        }
+        on error {
+                echo 'Failed to unsubscribe node $node from set $set';
+                exit 1;
+        }
+        echo 'unsubscribed node $node from set $set';
+};
+close SLONIK;
+run_slonik_script("/tmp/slonik-unsubscribe.$$");
--- /dev/null
+++ tools/altperl/show_nodes.pl
@@ -0,0 +1,212 @@
+#!perl # -*- perl -*-
+# $Id: show_nodes.pl,v 1.1.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+my @COST;
+my  @PATH;
+
+use Getopt::Long;
+
+require 'slon-tools.pm';
+require 'slon.env';
+my $FILE="/tmp/init-cluster.$$";
+open(SLONIK, ">$FILE");
+
+print SLONIK genheader();
+
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK "
+   init cluster (id = 1, comment = 'Node $dbname\@$dbhost');
+";
+close SLONIK;
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+foreach my $node (@NODES) {
+  if ($node > 1) {		# skip the first one; it's already initialized!
+    my ($dbname, $dbhost) = ($DBNAME[$node], $HOST[$node]);
+    print SLONIK "   store node (id = $node, comment = 'Node $node - $dbname\@$dbhost');\n";
+  }
+}
+
+print SLONIK "echo 'Set up replication nodes';
+";
+close SLONIK;
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+my @VIA ;
+generate_listen_paths();
+report_on_paths();
+print SLONIK qq[
+echo 'Next: configure paths for each node/origin';
+];
+foreach my $nodea (@NODES) {
+  my $dsna = $DSN[$nodea];
+  foreach my $nodeb (@NODES) {
+    if ($nodea != $nodeb) {
+      my $dsnb = $DSN[$nodeb];
+      my $providerba = $VIA[$nodea][$nodeb];
+      my $providerab = $VIA[$nodeb][$nodea];
+      if (!$printed[$nodea][$nodeb]) {
+	print SLONIK "      store path (server = $nodea, client = $nodeb, conninfo = '$dsna');\n";
+	$printed[$nodea][$nodeb] = "done";
+      }
+      if (!$printed[$nodeb][$nodea]) {
+	print SLONIK "      store path (server = $nodeb, client = $nodea, conninfo = '$dsnb');\n";
+	$printed[$nodeb][$nodea] = "done";
+      }
+      print SLONIK "echo 'configured path between $nodea and $nodeb';\n";
+    }
+  }
+}
+
+close SLONIK;
+
+run_slonik_script($FILE);
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+foreach my $origin (@NODES) {
+  my $dsna = $DSN[$origin];
+  foreach my $receiver (@NODES) {
+    if ($origin != $receiver) {
+      my $provider = $VIA[$origin][$receiver];
+      print SLONIK "      store listen (origin = $origin, receiver = $receiver, provider = $provider);\n";
+    }
+  }
+}
+
+print SLONIK qq[
+        echo 'Replication nodes prepared';
+        echo 'Please start the replication daemon on both systems';
+];
+
+close SLONIK;
+run_slonik_script($FILE);
+
+sub generate_listen_paths {
+  my @COST;
+  my @PATH;
+
+  my $infinity = 10000000;	# Initial costs are all infinite
+  foreach my $node1 (@NODES) {
+    foreach my $node2 (@NODES) {
+      $COST[$node1][$node2] = $infinity;
+    }
+  }
+
+  # Initialize paths between parents and children, and based on them,
+  # generate initial seeding of listener paths, @VIA
+
+  foreach my $node1 (@NODES) {
+    $COST[$node1][$node1] = 0;
+    $VIA[$node1][$node1] = 0;
+    foreach my $node2 (@NODES) {
+      if ($node2 != $node1) {
+	if ($PARENT[$node1] == $node2) {
+	  $PATH[$node1][$node2] = 1;
+	  $PATH[$node2][$node1] = 1;
+	  # Set up a cost 1 path between them
+	  # Parent to child
+	  $COST[$node1][$node2] = 1;
+	  $VIA[$node1][$node2] = $node1;
+
+	  # Child to parent
+	  $COST[$node2][$node1] = 1;
+	  $VIA[$node2][$node1] = $node2;
+	}
+      }
+    }
+  }
+
+  # Now, update the listener paths...
+  # 4 level nested iteration:
+  # 1 while not done, do
+  #   2 for each node, node1
+  #     3 for each node, node2, where node2 <> node1, where we don't
+  #           yet have a listener path
+  #       4 for each node node3 (<> node1 or node2),
+  #          consider introducing the listener path:
+  #                 node1 to node2 then node2 to node3
+  # In concept, it's an O(n^4) algorithm; since the number of nodes, n,
+  # is not likely to get particularly large, it's not worth tuning
+  # further.
+  $didwork = "yes";
+  while ($didwork eq "yes") {
+    $didwork = "no";
+    foreach my $node1 (@NODES) {
+      foreach my $node3 (@NODES) {
+	if (($VIA[$node3][$node1] == 0) && ($node3 != $node1)) {
+	  foreach my $node2 (@NODES) {
+	    if ($PATH[$node1][$node2] && ($VIA[$node2][$node3] != 0) && ($node2 != $node3) && ($node2 != $node1)) {
+	      # Consider introducing a path from n1 to n2 then n2 to n3
+	      # as a cheaper alternative to going direct from n1 to n3
+	      my $oldcost = $COST[$node3][$node1];
+	      my $newcost = $COST[$node1][$node2] + $COST[$node2][$node3];
+	      if ($newcost < $oldcost) {
+		$didwork = "yes";
+				# So we go via node 2
+		$VIA[$node3][$node1] = $node2;
+		$COST[$node3][$node1] = $newcost;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+sub report_on_paths {
+  print "cost\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d|", $node1;
+    foreach my $node2 (@NODES) {
+      if ($COST[$node2][$node1] == $infinity) {
+	printf "inf  ";
+      } else {
+	printf "%4d ", $COST[$node2][$node1];
+      }
+      print "\n";
+    }
+  }
+  print "\n\n";
+  print "VIA\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d", $node1;
+    foreach my $node2 (@NODES) {
+      printf "%4d ", $VIA[$node2][$node1];
+    }
+    print "\n";
+  }
+
+  print "PATHS\n";
+  print "    ";
+  foreach my $node2 (@NODES) {
+    printf "%4d|", $node2;
+  }
+  print "\n--------------------------------------------\n";
+  foreach my $node1 (@NODES) {
+    printf "%4d", $node1;
+    foreach my $node2 (@NODES) {
+      printf "%4d ", $PATH[$node2][$node1];
+    }
+    print "\n";
+  }
+}
--- /dev/null
+++ tools/altperl/restart_node.pl
@@ -0,0 +1,21 @@
+#!perl # -*- perl -*-
+# $Id: restart_node.pl,v 1.3.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($node) = @_;
+if ($node =~ /^node(\d+)$/) {
+  $nodenum = $node
+} else {
+  die "./restart_node nodeN\n";
+} 
+my $FILE="/tmp/restart.$$";
+
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+print SLONIK "restart node $node;\n";
+close SLONIK;
+run_slonik_script($FILE);
--- /dev/null
+++ tools/altperl/failover.pl
@@ -0,0 +1,43 @@
+#!perl # -*- perl -*-
+# $Id: failover.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($node1, $set1, $node2) = @ARGV;
+if ($node1 =~ /^node(\d+)$/) {
+  $node1 = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./failover.pl nodeN setOLD nodeNEW\n";
+}
+if ($set1 =~ /^set(\d+)$/) {
+  $set1 = $1;
+} else {
+  print "Valid set names are set1, set2, ...\n\n";
+  die "Usage: ./failover.pl nodeN setOLD nodeNEW\n";
+}
+if ($node2 =~ /^node(\d+)$/) {
+  $node2 = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./failover.pl nodeN setOLD nodeNEW\n";
+}
+
+open(SLONIK, ">/tmp/slonik.$$");
+print SLONIK genheader();
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK qq[
+try {
+      failover (id = $node1, backup node = $node2);
+} on error {
+      echo 'Failure to fail node $node1 over to $node2';
+      exit 1;
+}
+      echo 'Replication sets originating on $node1 failed over to $node2';
+];
+
+close SLONIK;
+run_slonik_script("/tmp/slonik.$$");
--- /dev/null
+++ tools/altperl/Makefile
@@ -0,0 +1,23 @@
+# ----------
+# Makefile for the HOWTOs
+#
+#	Copyright (c) 2003-2004, PostgreSQL Global Development Group
+#       $Id: Makefile,v 1.1.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+#
+# ----------
+
+slony_subdir = tools/altperl
+slony_top_builddir = ../..
+include $(slony_top_builddir)/Makefile.global
+
+DISTFILES = Makefile ToDo README $(wildcard *.pl*) $(wildcard *.pm*) $(wildcard *.env*)
+
+distdir: $(DISTFILES)
+	mkdir $(distdir)/$(subdir)
+	-chmod 777 $(distdir)/$(subdir)
+	for file in $(DISTFILES) ; do \
+      cp $$file $(distdir)/$(subdir)/$$file ; \
+    done
+
+clean distclean maintainer-clean:
+
--- /dev/null
+++ tools/altperl/slon_watchdog2.pl
@@ -0,0 +1,82 @@
+#!perl # -*- perl -*-
+# $Id: slon_watchdog2.pl,v 1.2.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+$node =$ARGV[0];
+$sleep =$ARGV[1];
+
+if ( scalar(@ARGV) < 2 ) {
+  die "Usage: ./slon_watchdog node sleep-time\n";
+}
+
+if ($node =~/^node(\d+)$/) {
+  $nodenum = $1;
+}
+
+log_to_watchdog_log("Invoking watchdog for $SETNAME node $nodenum");
+while (1) {
+  my $res = query_slony_status($nodenum);    # See where the node stands
+  my $eventsOK;
+  if ($res =~ /^\s*f\s*\|/) {
+    $eventsOK = "YES";
+  } else {
+    $eventsOK = "NO";
+  }
+  my $pid = get_pid($node);                  # See if the slon process is alive
+  my ($restart, $kick);
+  $kick = "NO";   # Initially, assume we don't need to submit a "restart node" command
+  if ($pid) {  # PID is alive...
+    if ($eventsOK eq "YES") {
+      # All is well - do nothing!
+      $restart = "NO";
+    } else {
+      $restart = "YES";
+    }
+  } else {
+    $restart = "YES";
+    # See if the slon log ends with "FATAL  localListenThread: Another slon daemon is serving this node already"
+    my $lastlog=`/bin/ls -t $LOGDIR/slony1/node$nodenum/$dbname*log | head -1`;
+    my $lastline=`tail -1 $lastlog`;
+    if ($lastline =~ /Another slon daemon is serving this node already/) {
+      $kick = "YES";   # Yup, need to tell slonik to reset this node
+    }
+  }
+
+  # If the node needs a swift kick in the "RESTART", then submit that to slonik
+  if ($kick eq "YES") {
+    log_to_watchdog_log("submit slonik to restart $SETNAME node $nodenum");
+    open(SLONIK, "|$SLON_BIN_PATH/slonik");
+    print SLONIK genheader();
+    print SLONIK "restart node $node\n";
+    close SLONIK;
+  }
+  if ($restart eq "YES") {
+    if ($pid) {
+      log_to_watchdog_log("terminate slon daemon for $SETNAME node $nodenum");
+      # Kill slon until dead...
+      kill 2, $pid;
+      sleep 3;
+      kill 15, $pid;
+      sleep 3;
+      kill 9, $pid;
+    }
+    log_to_watchdog_log("restart slon for $nodenum");
+    start_slon($nodenum);
+  }
+  sleep $sleep;
+}
+
+
+sub log_to_watchdog_log {
+  my ($message) = @_;
+  chomp $message;
+  my $date = `date`;
+  chomp $date;
+  open (SLONLOG, ">>$LOGDIR/slony-watchdog.log");
+  print SLONLOG $date, "|", $message, "\n";
+  close SLONLOG;
+}
--- /dev/null
+++ tools/altperl/slon-tools.pm
@@ -0,0 +1,165 @@
+#!perl     # -*- perl -*-
+# $Id: slon-tools.pm,v 1.11.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+sub add_node {
+  my %PARAMS = (host=> undef,
+		dbname => 'template1',
+		port => 5432,
+		user => 'postgres',
+		node => undef,
+		password => undef,
+		parent => 1,
+		noforward => undef
+	       );
+  my $K;
+  while ($K= shift) {
+    $PARAMS{$K} = shift;
+  }
+   die ("I need a node number") unless $PARAMS{'node'};
+  my $node = $PARAMS{'node'};
+  push @NODES, $node;
+  my $loginstr;
+  my $host = $PARAMS{'host'};
+  if ($host) {
+    $loginstr .= "host=$host";
+    $HOST[$node] = $host;
+  } else {
+    die("I need a host name") unless $PARAMS{'host'};
+  }
+  my $dbname = $PARAMS{'dbname'};
+  if ($dbname) {
+    $loginstr .= " dbname=$dbname";
+    $DBNAME[$node] = $dbname;
+  }
+  my $user=$PARAMS{'user'};
+  $loginstr .= " user=$user";
+  $USER[$node]= $user;
+
+  my $port = $PARAMS{'port'};
+  if ($port) {
+    $loginstr .= " port=$port";
+    $PORT[$node] = $port;
+  } else {
+    die ("I need a port number");
+  }
+  my $password = $PARAMS{'password'};
+  if ($password) {
+    $loginstr .= " password=$password";
+    $PASSWORD[$node] = $password;
+  }
+  $DSN[$node] = $loginstr;
+  my $parent = $PARAMS{'parent'};
+  if ($parent) {
+    $PARENT[$node] = $parent;
+  }
+  my $noforward = $PARAMS{'noforward'};
+  if ($noforward) {
+    $NOFORWARD[$node] = $noforward;
+  }
+}
+
+# This is the usual header to a slonik invocation that declares the
+# cluster name and the set of nodes and how to connect to them.
+sub genheader {
+  my $header = "cluster name = $SETNAME;\n";
+  foreach my $node (@NODES) {
+    if ($DSN[$node]) {
+      my $dsn = $DSN[$node];
+      $header .= " node $node admin conninfo='$dsn';\n";
+    }
+  }
+  return $header;
+}
+
+# Stores copy of slonik script in log file in $LOGDIR
+# then invokes it and deletes it
+sub run_slonik_script {
+  my ($script) = @_;
+  chomp $script;
+  open(OUT, ">>$LOGDIR/slonik_scripts.log");
+  my $now = `date`;
+  chomp $now;
+  print OUT "# -------------------------------------------------------------\n";
+  print OUT "# Script: $script submitted at $now \n";
+  print OUT "# -------------------------------------------------------------\n";
+  close OUT;
+  `cat $script >> $LOGDIR/slonik_scripts.log`;
+  #print `slonik < $script`;
+  print `cat $script`;
+  unlink($script);
+}
+
+sub ps_args {
+  my $sys=`uname`;
+  chomp $sys;   # strip off edges
+  if ($sys eq "Linux") {
+    return "/bin/ps -auxww";
+  } elsif ($sys eq "FreeBSD") {
+    return "/bin/ps -auxww";
+  } elsif ($sys eq "SunOS") {
+    return "/usr/ucb/ps -auxww";
+  } elsif ($sys eq "AIX") {
+    return "/usr/bin/ps auxww";
+  } 
+  return "/usr/bin/ps -auxww";    # This may be questionable for other systems; extend as needed!    
+}
+
+sub get_pid {
+  my ($node) = @_;
+  $node =~ /node(\d*)$/;
+  my $nodenum = $1;
+  my $pid;
+  my $tpid;
+  my ($dbname, $dbport, $dbhost) = ($DBNAME[$nodenum], $PORT[$nodenum], $HOST[$nodenum]);
+  #  print "Searching for PID for $dbname on port $dbport\n";
+  my $command =  ps_args() . "| egrep \"[s]lon .*$SETNAME\" | egrep \"host=$dbhost dbname=$dbname.*port=$dbport\" | sort -n | awk '{print \$2}'";
+  #print "Command:\n$command\n";
+  open(PSOUT, "$command|");
+  while ($tpid = <PSOUT>) {
+    chomp $tpid;
+    $pid = $tpid;
+  }
+  close(PSOUT);
+  return $pid;
+}
+
+sub start_slon {
+  my ($nodenum) = @_;
+  my ($dsn, $dbname) = ($DSN[$nodenum], $DBNAME[$nodenum]);
+  my $cmd;
+  `mkdir -p $LOGDIR/slony1/node$nodenum`;
+  if ($APACHE_ROTATOR) {
+    $cmd = "$SLON_BIN_PATH/slon -s 1000 -d2 $SETNAME '$dsn' 2>&1 | $APACHE_ROTATOR \"$LOGDIR/slony1/node$nodenum/" . $dbname . "_%Y-%m-%d_%H:%M:%S.log\" 10M&";
+  } else {
+    $cmd = "$SLON_BIN_PATH/slon -s 1000 -d2 $SETNAME '$dsn' 2>&1 > $LOGDIR/slony1/node$nodenum/$dbname.log &";
+  }
+  print "Invoke slon for node $nodenum - $cmd\n";
+  system $cmd;
+}
+
+sub query_slony_status {
+  my ($nodenum) = @_;
+  my $query = qq{
+  select now() - ev_timestamp > '00:40:00'::interval as event_old, now() - ev_timestamp as age,
+       ev_timestamp, ev_seqno, ev_origin as origin
+from _$SETNAME.sl_event events, _$SETNAME.sl_subscribe slony_master
+  where 
+     events.ev_origin = slony_master.sub_provider and
+     not exists (select * from _$SETNAME.sl_subscribe providers 
+                  where providers.sub_receiver = slony_master.sub_provider and
+                        providers.sub_set = slony_master.sub_set and
+                        slony_master.sub_active = 't' and
+                        providers.sub_active = 't')
+order by ev_origin desc, ev_seqno desc limit 1;
+};
+  my ($port, $host, $dbname)= ($PORT[$nodenum], $HOST[$nodenum], $DBNAME[$nodenum]);
+  my $result=`$SLON_BIN_PATH/psql -p $port -h $host -c "$query" --tuples-only $dbname`;
+  chomp $result;
+  #print "Query was: $query\n";
+  #print "Result was: $result\n";
+  return $result;
+}
+
+1;
--- /dev/null
+++ tools/altperl/drop_node.pl
@@ -0,0 +1,30 @@
+#!perl # -*- perl -*-
+# $Id: drop_node.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($node) = @ARGV;
+if ($node =~ /^node(\d+)$/) {
+  $node = $1;
+} else {
+  print "Need to specify node!\n";
+  die "drop_node nodeN\n";
+}
+
+my $OUTPUTFILE="/tmp/slonik-drop.$$";
+open(SLONIK, ">$OUTPUTFILE");
+print SLONIK genheader();
+print SLONIK qq{
+try {
+      drop node (id = $node);
+} on error {
+      echo 'Failed to drop node $node from cluster';
+      exit 1;
+}
+echo 'dropped node $node cluster';
+};
+close SLONIK;
+run_slonik_script($OUTPUTFILE);
--- /dev/null
+++ tools/altperl/ToDo
@@ -0,0 +1,82 @@
+- Need to write a "repair_cluster.pl" script that 
+  modifies configuration to reflect new configuration,
+  dropping and adding paths and listeners.
+
+  This would compare the configuration computed (as in init_cluster)
+  with the configuration actually found on some node, add "additional"
+  bits, and drop obsolete bits.
+
+- It would seem likely that the function "generate_listen_paths()" in
+  init_cluster.pl would be beneficial to port to pl/pgsql, as
+  there presently isn't any capability to rebuild the listener
+  paths by automatically dropping the old ones.
+
+- At present, the configuration generated by this set of tools is
+  fairly fragile.  If just about any sort of error is made, it is
+  commonly needful to drop all of the Slony schemas, thereby cleaning
+  _everything_ out, and restarting the configuration process from
+  scratch.
+
+  That certainly isn't ideal.
+
+
+-------------------------------------------------------------------------------------------------
+
+More about the "generating SET LISTEN" calculation
+------------------------------------------------------------------------
+
+I have been mulling over the notion of setting up the Slonik STORE
+LISTEN(ORIGIN=a, RECEIVER=b, PROVIDER=c) configuration via a stored
+procedure, using in-the-DBMS data.
+
+The "features" of this idea:
+
+This involves having a table (view?) containing the intended parentage
+for each node, that is, which nodes point to which parents.
+
+This would allow the following good things:
+
+- Can't drop a node that has children, probably adding in other
+possible data checks
+
+- Can calculate the full "listener matrix" within pl/pgsql instead of
+doing it in Perl (take a look at init_cluster.pl, subroutine
+generate_listen_paths()).
+
+My preliminary thinking about it was pointing to there being a pretty
+elegant way to do this using SQL queries that might be more readable
+than the dynamic programming formulation embedded in that subroutine.
+(I didn't write out the Bellman equations, but took a look back at my
+old optimization texts ;-).)
+
+The primary problem that this solves is to create those STORE LISTEN()
+definitions, which get pretty involved to generate by hand if you get
+more than three nodes.
+
+In doing some further thinking, I noticed a couple of conspicuous
+challenges:
+
+1. There can be no fixed association with sets, as the sl_listen table
+does not contain set fields, and different sets can use differently
+shaped subscription trees.
+
+(I think users would be doing something pretty stupid to have _wildly_
+different arrangements for different sets, but I still have to support
+it...)
+
+2. The tree cannot be based on subscriptions because it needs to exist
+before any subscriptions are established
+
+In effect, I have no fixed place where I can get the information at
+the point at which I most need it.
+
+Once all nodes are subscribed, I could use subscription information to
+weight the cost functions, but I need the data BEFORE we do anything.
+
+This isn't pointing yet to a good approach to "seeding" it; if someone
+has some inspiration, let me know...
+-- 
+Christopher Browne
+<cbbrowne at acm.org>
+
+-------------------------------------------------------------------------------------------------
--- /dev/null
+++ tools/altperl/restart_nodes.pl
@@ -0,0 +1,20 @@
+#!perl # -*- perl -*-
+# $Id: restart_nodes.pl,v 1.2.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my $FILE="/tmp/restart.$$";
+foreach my $node (@NODES) {
+  my $dsn = $DSN[$node];
+  open(SLONIK, ">$FILE");
+  print SLONIK qq{
+	cluster name = $SETNAME ;
+	node $node admin conninfo = '$dsn';
+	restart node $node;
+    };
+  close SLONIK;
+  run_slonik_script($FILE);
+}
--- /dev/null
+++ tools/altperl/slon_watchdog.pl
@@ -0,0 +1,48 @@
+#!perl # -*- perl -*-
+# $Id: slon_watchdog.pl,v 1.3.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+$node =$ARGV[0];
+$sleep =$ARGV[1];
+
+if ( scalar(@ARGV) < 2 ) {
+  die "Usage: ./slon_watchdog node sleep-time\n";
+}
+
+if ($node =~/^node(\d+)$/) {
+  $nodenum = $1;
+}
+
+slon_watchdog($node, $nodenum);
+
+sub slon_watchdog {
+  my ($node, $nodenum) = @_;
+  $pid = get_pid($node);
+  if (!($pid)) {
+    my ($dsn, $dbname) = ($DSN[$nodenum], $DBNAME[$nodenum]);
+    open (SLONLOG, ">>$LOGDIR/slon-$dbname-$node.err");
+    print SLONLOG "WATCHDOG: No Slon is running for node $node!\n";
+    print SLONLOG "WATCHDOG: You ought to check the postmaster and slon for evidence of a crash!\n";
+    print SLONLOG "WATCHDOG: I'm going to restart slon for $node...\n";
+    # First, restart the node using slonik
+    system "./restart_node.sh $node";
+    # Next, restart the slon process to service the node
+    start_slon($nodenum);
+    $pid = get_pid($node);
+    print SLONLOG "WATCHDOG: Restarted slon for set $SETNAME, PID $pid\n";
+  } else {
+    open(LOG, ">>$LOGDIR/slon_watchdog.log");
+    print LOG "\n";
+    system "date >> $LOGDIR/slon_watchdog.log";
+    print LOG "Found slon daemon running for set $SETNAME, PID $pid\n";
+    print LOG "Looks Ok\n";
+    print LOG "Sleeping for $sleep seconds\n";
+  }
+  close(PSOUT);
+  sleep $sleep;
+  slon_watchdog();
+}
--- /dev/null
+++ tools/altperl/uninstall_nodes.pl
@@ -0,0 +1,28 @@
+#!perl # -*- perl -*-
+# $Id: uninstall_nodes.pl,v 1.2.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+$FILE="/tmp/slonik.$$";
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+print SLONIK qq{
+	uninstall node (id=1);
+};
+close SLONIK;
+run_slonik_script($FILE);
+
+foreach my $node (@NODES) {
+  foreach my $command ("drop schema _$SETNAME cascade;") {
+    print $command, "\n";
+    print `echo "$command" | psql -h $HOST[$node] -U $USER[$node] -d $DBNAME[$node] -p $PORT[$node]`;
+  }
+  foreach my $t (@SERIALTABLES) {
+    my $command = "alter table $t drop column \\\"_Slony-I_" . $SETNAME . "_rowID\\\";";
+    print $command, "\n";
+    print `echo "$command" | psql -h $HOST[$node] -U $USER[$node] -d $DBNAME[$node] -p $PORT[$node]`;
+  }
+}
--- /dev/null
+++ tools/altperl/move_set.pl
@@ -0,0 +1,51 @@
+#!perl # -*- perl -*-
+# $Id: move_set.pl,v 1.3.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+my ($set, $node1, $node2) = @ARGV;
+if ($set =~ /^set(\d+)$/) {
+  # Node name is in proper form
+  $set = $1;
+} else {
+  print "Valid set names are set1, set2, ...\n\n";
+  die "Usage: ./move_set.pl setN nodeOLD nodeNEW\n";
+}
+
+if ($node1 =~ /^node(\d+)$/) {
+  $node1 = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./move_set.pl setN nodeOLD nodeNEW\n";
+}
+if ($node2 =~ /^node(\d+)$/) {
+  $node2 = $1;
+} else {
+  print "Valid node names are node1, node2, ...\n\n";
+  die "Usage: ./move_set.pl setN nodeOLD nodeNEW\n";
+}
+
+open(SLONIK, ">/tmp/slonik.$$");
+print SLONIK genheader();
+my ($dbname, $dbhost)=($DBNAME[1], $HOST[1]);
+print SLONIK qq[
+        try {
+                echo 'Locking down set $set on node $node1';
+                lock set (id = $set, origin = $node1);
+                echo 'Locked down - moving it';
+                move set (id = $set, old origin = $node1, new origin = $node2);
+                unlock set (id = $set, origin = $node2);
+        }
+        on error {
+                echo 'Failure to move set $set from $node1 to $node2';
+                unlock set (id = $set, origin = $node1);
+                exit 1;
+        }
+        echo 'Replication set $set moved from node $node1 to $node2';
+];
+
+close SLONIK;
+run_slonik_script("/tmp/slonik.$$");
--- /dev/null
+++ tools/altperl/show_configuration.pl
@@ -0,0 +1,38 @@
+#!perl #-*- perl -*-
+# $Id: show_configuration.pl,v 1.1.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+# This script simply displays an overview of node configuration
+# for a given SLONY node set
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+print "Slony Configuration\n-------------------------------------\n";
+if ($ENV{"SLONYNODES"}) {
+  print "With node configuration from ", $ENV{"SLONYNODES"}, "\n";
+}
+if ($ENV{"SLONYSET"}) {
+  print "With set configuration from ", $ENV{"SLONYSET"}, "\n";
+}
+
+print qq{
+Slony-I Cluster: $SETNAME
+Logs stored under $LOGDIR
+Slony Binaries in: $SLON_BIN_PATH
+};
+if ($APACHE_ROTATOR) {
+  print "Rotating logs using Apache Rotator: $APACHE_ROTATOR\n";
+}
+print qq{
+Node information
+--------------------------------
+};
+foreach $node (0..100) {
+  if ($DSN[$node]) {
+    printf("Node: %2d Host: %15s User: %8s Port: %4d Forwarding? %4s Parent: %2d Database: %10s\n         DSN: %s\n",
+	   $node, $HOST[$node], $USER[$node], $PORT[$node], $NOFORWARD[$node],
+	   $PARENT[$node], $DBNAME[$node], $DSN[$node]);
+  }
+}
--- /dev/null
+++ tools/altperl/build_env.pl
@@ -0,0 +1,108 @@
+#!perl    # -*- perl -*-
+# $Id: build_env.pl,v 1.5.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Contributed by:
+# Joe Kalash
+# kalash at savicom.net
+
+# This script, given parameters concerning the database nodes,
+# generates output for "slon.env" consisting of:
+# - A set of add_node() calls to configure the cluster
+# - The arrays @KEYEDTABLES, @SERIALTABLES, and @SEQUENCES
+
+use DBI;
+use Getopt::Long;
+use strict;
+
+my $dataBase;
+my $host;
+my $dataBaseUser;
+my $dataBasePassword;
+my $dataBasePort;
+my @nodes;
+my $usage = "$0 -node host:database:user[:password:port] [-node ...]
+First node is assumed to be the master.\n";
+
+&usage if(!GetOptions('node=s@'=>\@nodes));
+
+die "At least one node is required" if ( scalar(@nodes) < 1 );
+
+my $nodeNumber = 1;
+my $parentString;
+foreach my $node (@nodes)
+{
+  my($tmpHost,$tmpDataBase,$tmpDataBaseUser,$tmpDataBasePassword,$tmpPort) =
+    split(/:/,$node);
+  die "Host is required" if ( !$tmpHost );
+  die "database is required" if ( !$tmpDataBase );
+  die "user is required" if ( !$tmpDataBaseUser );
+  $tmpPort = 5432 if ( !$tmpPort );
+  $host = $tmpHost if ( !$host );
+  $dataBase = $tmpDataBase if ( !$dataBase );
+  if ( !$dataBaseUser ) {
+    $dataBaseUser = $tmpDataBaseUser;
+    $dataBasePassword = $tmpDataBasePassword if ( $tmpDataBasePassword );
+    $dataBasePort = $tmpPort if ( $tmpPort );
+  }
+  print "&add_node(host => '$tmpHost', dbname => '$tmpDataBase', port =>$tmpPort,
+        user=>'$tmpDataBaseUser', password=>'$tmpDataBasePassword', node=>$nodeNumber $parentString);\n";
+  $parentString = ', parent=>1';
+  $nodeNumber++;
+
+}
+my $connectString = "dbi:Pg:dbname=$dataBase;host=$host;port=$dataBasePort";
+my $dbh = DBI->connect($connectString,$dataBaseUser,$dataBasePassword,
+		       {RaiseError => 0, PrintError => 0, AutoCommit => 1});
+die "connect: $DBI::errstr" if ( !defined($dbh) || $DBI::err );
+# Read in all the user 'normal' tables in public.
+my $tableQuery = $dbh->prepare("
+SELECT pg_namespace.nspname || '.' || pg_class.relname,pg_class.relkind,pg_class.relhaspkey 
+FROM pg_namespace,pg_class
+WHERE pg_class.reltype > 0
+AND pg_class.relnamespace = pg_catalog.pg_namespace.oid
+AND (pg_class.relkind = 'r' OR pg_class.relkind = 'S')
+AND pg_namespace.nspname = 'public' AND pg_namespace.oid = pg_class.relnamespace");
+
+die "prepare(tableQuery): $DBI::errstr" if ( !defined($tableQuery) || $DBI::err );
+die "execute(tableQuery): $DBI::errstr" if ( !$tableQuery->execute() );
+
+my @tablesWithIndexes;
+my @tablesWithoutIndexes;
+my @sequences;
+while ( my $row = $tableQuery->fetchrow_arrayref() ) {
+  my $relname = @$row[0];
+  my $relkind = @$row[1];
+  my $relhaspkey = @$row[2];
+  push(@sequences,$relname) if ( $relkind eq 'S' );
+  push(@tablesWithIndexes,$relname) if ( $relkind eq 'r' && $relhaspkey == 1 );
+  push(@tablesWithoutIndexes,$relname) if ( $relkind eq 'r' && $relhaspkey == 0 );
+}
+$tableQuery->finish();
+$dbh->disconnect();
+
+if ( scalar(@tablesWithIndexes) > 1 ) {
+  print '@KEYEDTABLES=(' . "\n";
+  foreach my $table (sort @tablesWithIndexes) {
+    print "\t\"$table\",\n";
+  }
+  print ");\n";
+}
+if ( scalar(@tablesWithoutIndexes) > 1 ) {
+  print '@SERIALTABLES=(' . "\n";
+  foreach my $table (sort @tablesWithoutIndexes) {
+    print "\t\"$table\",\n";
+  }
+  print ");\n";
+}
+if ( scalar(@sequences) > 1 ) {
+  print '@SEQUENCES=(' . "\n";
+  foreach my $table (sort @sequences) {
+    print "\t\"$table\",\n";
+  }
+  print ");\n";
+}
+exit 0;
+
+sub usage {
+  print "$usage";
+  exit 0;
+}
--- /dev/null
+++ tools/altperl/slon_pushsql.pl
@@ -0,0 +1,40 @@
+#!perl # -*- perl -*-
+# $Id: slon_pushsql.pl,v 1.5.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+my ($set, $node, $file) = @ARGV;
+if ($set =~ /^set(\d+)$/) {
+  $set = $1;
+} else {
+  print "Invalid set identifier";
+  die "Usage: ./slon_pushsql.pl set[N] node[N] full_path_to_sql_script_file\n";
+}
+if ($node =~ /^node(\d+)$/) {
+  $node = $1;
+} else {
+  print "Invalid node identifier";
+  die "Usage: ./slon_pushsql.pl set[N] node[N] full_path_to_sql_script_file\n";
+}
+
+if ($file =~ /^\//) {
+} else {
+  print "SQL script path needs to be a full path, i.e. /tmp/my_script.sql\n";
+  die "Usage: ./slon_pushsql.pl set[N] node[N] full_path_to_sql_script_file\n";
+}
+
+my $FILE="/tmp/gensql.$$";
+open(SLONIK, ">$FILE");
+print SLONIK genheader();
+
+print SLONIK qq{
+  execute script (
+    set id=$set,
+    filename='$file',
+    event node = $node
+  );
+};
+close SLONIK;
+run_slonik_script($FILE);
--- /dev/null
+++ tools/altperl/slon_kill.pl
@@ -0,0 +1,47 @@
+#!perl # -*- perl -*-
+# $Id: slon_kill.pl,v 1.4.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Kill all slon instances for the current setname
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+print "slon_kill.pl...   Killing all slon and slon_watchdog instances for setname $SETNAME\n";
+print "1.  Kill slon watchdogs\n";
+#kill the watchdog
+
+open(PSOUT, ps_args() . " | egrep '[s]lon_watchdog' | sort -n | awk '{print \$2}'|");
+$found="n";
+while ($pid = <PSOUT>) {
+  chomp $pid;
+  if (!($pid)) {
+    print "No slon_watchdog is running for set $SETNAME!\n";
+  } else {
+    $found="y";
+    kill 9, $pid;
+    print "slon_watchdog for set $SETNAME killed - PID [$pid]\n";
+  }
+}
+close(PSOUT);
+if ($found eq 'n') {
+  print "No watchdogs found\n";
+}
+print "\n2. Kill slon processes\n";
+#kill the slon daemon
+$found="n";
+open(PSOUT, ps_args() . " | egrep \"[s]lon .*$SETNAME\" | sort -n | awk '{print \$2}'|");
+while ($pid = <PSOUT>) {
+  chomp $pid;
+  if (!($pid)) {
+    print "No Slon is running for set $SETNAME!\n";
+  } else {
+    kill 9, $pid;
+    print "Slon for set $SETNAME killed - PID [$pid]\n";
+    $found="y";
+  }
+}
+close(PSOUT);
+if ($found eq 'n') {
+  print "No slon processes found\n";
+}
--- /dev/null
+++ tools/altperl/replication_test.pl
@@ -0,0 +1,187 @@
+#!perl   # -*- perl -*-
+# $Id: replication_test.pl,v 1.1.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Christopher Browne
+# Copyright 2004
+# Afilias Canada
+
+# This script, given DSN parameters to access a Slony-I cluster,
+# submits insert, update, and delete requests and sees how they
+# propagate through the system.
+
+# It requires setting up the following table and propagating it via
+# (surprise!) Slony-I
+
+#create table slony_test (
+#   description text,
+#   mod_date timestamp with time zone
+#);
+
+use Pg;
+use Getopt::Long;
+use strict;
+
+$LOGDIR="/opt/OXRS/logs/general";
+`mkdir -p $LOGDIR`;
+$HOST = `hostname`;
+$IDENT = "-";
+$USER = `whoami`;
+$SIZE = "-";
+chomp($HOST, $IDENT, $USER, $SIZE);
+my $sleep_seconds = 80;
+
+$goodopts = GetOptions("help", "database=s", "host=s", "user=s", "cluster=s", "password=s", "port=s", "set=s");
+if (defined($opt_help)) {
+  show_usage();
+}
+my ($database,$user, $port, $cluster, $host, $password, $set);
+
+$database = $opt_database if (defined($opt_database));
+$port = 5432;
+$port = $opt_port if (defined($opt_port));
+$user = $opt_user if (defined($opt_user));
+$password = $opt_password if (defined($opt_password));
+$host = $opt_host if (defined($opt_host));
+$cluster = $opt_cluster if (defined($opt_cluster));
+$set = $opt_set if (defined($opt_set));
+
+#DBI: my $initialDSN = "dbi:Pg:dbname=$database;host=$host;port=$port";
+my $initialDSN = "dbname=$database host=$host port=$port";
+$initialDSN = $initialDSN . " password=$password" if defined($opt_password);
+
+# DBI: my $dbh = DBI->connect($initialDSN, $user, $password,
+# 		       {RaiseError => 0, PrintError => 0, AutoCommit => 1});
+# die "connect: $DBI::errstr" if ( !defined($dbh) || $DBI::err );
+my $dbh = Pg::connectdb($initialDSN);
+
+# Query to find the "master" node
+my $masterquery = qq{
+  select sub_provider 
+   from _oxrslive.sl_subscribe s1 
+   where not exists (select * from _oxrslive.sl_subscribe s2 
+                                where s2.sub_receiver = s1.sub_provider and 
+                                           s1.sub_set = $set and s2.sub_set = $set and
+                                           s1.sub_active = 't' and s2.sub_active = 't')
+   group by sub_provider;
+};
+
+# my $tq = $dbh->prepare($masterquery);
+# die "prepare(masterquery): $DBI::errstr" if (!defined($tq) || $DBI::err);
+# die "execute(masterquery): $DBI::errstr" if (!$tq->execute());
+my $tq = $dbh->exec($masterquery);
+
+my $masternode;
+#while (my $row = $tq->fetchrow_arrayref()) {
+while (my @row = $tq->fetchrow) {
+  ($masternode) = @row;
+}
+#$tq->finish();
+
+# Query to find live DSNs
+my $dsnsquery =
+qq {
+   select p.pa_server, p.pa_conninfo
+   from _$cluster.sl_path p
+   where exists (select * from _$cluster.subscribe s where 
+                          s.sub_set = $set and 
+                          (s.sub_provider = p.pa_server or s.sub_receiver = p.pa_server)
+                          sub_active = 't')
+   group by pa_server, pa_conninfo;
+};
+
+# $tq = $dbh->prepare($dsnsquery);
+# die "prepare(dsnsquery): $DBI::errstr" if (!defined($tq) || $DBI::err);
+# die "execute(dsnsquery): $DBI::errstr" if (!$tq->execute());
+$tq = $dbh->exec($dsnsquery);
+my @DSN;
+#while (my $row = $tq->fetchrow_arrayref()) {
+while (my @row = $tq->fetchrow) {
+  ($node, $dsn) = @row;
+  $DSN{$node} = $dsn;
+}
+#$tq->finish();
+
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+my $time_date = sprintf ("%d-%.2d-%.2d %.2d:%.2d:%.2d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
+
+my $insert_text = "INSERT Replication Test";
+my $insert_query = "insert into slony_test (description, mod_date) values ('$insert_text', '$time_date');";
+foreach my $source (sort keys %SOURCES) {
+  submit_to_master($masternode, $insert_query, 1);
+}
+
+sleep $sleep_seconds;
+
+sub submit_to_master {
+  my ($node, $query, $expected_count) = @_;
+  my $dsn = $DSN{$node};
+  my $master = Pg::connectdb($conn_info);
+  my $status_conn = $master->status;
+  if ($status_conn ne PGRES_CONNECTION_OK) {
+    alog($source, "", "Master connection Query failed - " . $master->errorMessage, $status_conn);
+    report_failed_conn($conn_info);
+    return -1;
+  }
+  my $result = $master->exec($query);
+  if ($result->ntuples != $expect_count) {
+    alog($source, "", "Master $query failed - unexpected tuple count", $result->cmdTuples);
+    return -2;
+  } else {
+    alog($source, "", "Master $query succeeded", 0);
+    return 0;
+  }
+}
+
+sub submit_to_slave {
+  my ($source, $dest, $conn_info, $query, $expect_count)=@_;
+  my $slave = Pg::connectdb($conn_info);
+  my $status_conn = $slave->status;
+  if ($status_conn ne PGRES_CONNECTION_OK) {
+    alog($source, $dest, "Connection Query failed!", -1);
+    return "Connection Failed";
+  }
+  my $result = $slave->exec($query);
+  if ($result->resultStatus != 2) {
+    alog($source, $dest, "Slave query $query failed", $result->resultStatus);
+    report_failed_conn($conn_info);
+    return "Query Failed";
+  } elsif ($result->ntuples != $expect_count) {
+    alog($source, $dest, "Slave query failed - $query - slave is behind master", -3);
+    # This indicates that the slave is behind - issue message!
+    return "Slave Behind";
+  } else {
+    alog($source, $dest, "Query $query succeeded", 0);
+    return "OK";
+  }
+}
+
+sub report_failed_conn {
+    my ($ci) = @_;
+    $ci =~ s/password=.*$//g;
+    print "Failure - connection to $ci\n";
+}
+
+sub alog {
+  my ($source, $dest, $message, $rc) = @_;
+  chomp ($source, $dest, $message, $rc);
+  #print"Master DB: $source  Slave DB: $dest  Message: [$message] RC=$rc\n"; 
+  apache_log("replication.log", "Master DB: $source  Slave DB: $dest  Message: [$message]", $rc);
+}
+
+sub apache_log {
+  my ($logfile, $request, $status) = @_;
+  my $date = `date`;
+  chomp($request, $status, $date);
+  my $LOGENTRY ="$HOST $IDENT $USER [$date] \"$request\" $status $SIZE";
+  open(OUTPUT, ">>$LOGDIR/$logfile");
+  print OUTPUT $LOGENTRY, "\n";
+  close OUTPUT;
+}
+
+sub show_usage {
+  my ($inerr) = @_;
+  if ($inerr) {
+    chomp $inerr;
+    print $inerr, "\n";
+  }
+  die "$0  --host --database --user --cluster --port --password";
+}
--- /dev/null
+++ tools/altperl/update_nodes.pl
@@ -0,0 +1,16 @@
+#!perl # -*- perl -*-
+# $Id: update_nodes.pl,v 1.2.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+# Author: Christopher Browne
+# Copyright 2004 Afilias Canada
+
+require 'slon-tools.pm';
+require 'slon.env';
+
+open(SLONIK, ">/tmp/update_nodes.$$");
+print SLONIK genheader();
+
+foreach my $node (@NODES) {
+  print SLONIK "update functions (id = $node);\n";
+};
+close SLONIK;
+run_slonik_script("/tmp/update_nodes.$$");
--- /dev/null
+++ tools/altperl/README
@@ -0,0 +1,129 @@
+README
+$Id: README,v 1.6.2.1 2004/09/30 17:37:28 cbbrowne Exp $
+
+Christopher Browne
+Database Administrator
+Afilias Canada
+
+This is a "second system" set of scripts for managing a set of Slony-I
+instances.
+
+Unlike the shell scripts that have previously been used, these scripts
+support having an arbitrary number of Slony-I nodes.  They are
+configured in the [cluster].nodes file (e.g. - environment variable
+SLONYNODES) by calling add_node() once indicating the configuration
+for each node that is needed.
+
+The following configuration is set up:
+
+ Host Level Configuration
+--------------------------------------
+
+This configuration will normally apply to all clusters being managed
+on a particular host, so it would probably make sense to modify it
+directly in slon.env.
+
+ $SLON_BIN_PATH is the path to use to find the slon and slonik
+   binaries.
+
+ $APACHE_ROTATOR is an optional reference to the location of the
+   Apache log rotator; if you set it to a path to an Apache "rotatelog"
+   program, that will be used to keep log file size down to a "dull
+   roar".
+  <http://httpd.apache.org/docs-2.0/programs/rotatelogs.html>
+
+ $LOGDIR is the directory in which to put log files.  The script will
+   generate a subdirectory for each node.
+
+ Node Level Configuration
+--------------------------------------
+
+This configuration should be set up in the file represented in the
+environment variable SLONYNODES.
+
+ $SETNAME represents the name of the cluster.  In each database
+  involved in the replication set, you will find the namespace
+  "_$SETNAME" that contains Slony-I's configuration tables
+
+
+ Set Level Configuration
+-----------------------------------
+
+The configuration of the tables, sequences and such are stored in the
+file pointed to by the environment variable SLONYSET, in the following
+Perl variables:
+
+  $TABLE_ID - where to start numbering table IDs
+
+   The table IDs are required to be unique across all sets in a
+   Slony-I cluster, so if you add extra sets, you need to set
+   $TABLE_ID to a value that won't conflict, typically something
+   higher than largest value used in earlier sets.
+
+  @PKEYEDTABLES contains all of the tables that have primary keys
+
+  %KEYEDTABLES contains tables with candidate primary keys associated
+    with the index you _want_.
+
+  @SERIALTABLES contains tables that do not have a unique key
+                to which Slony-I will need to add and populate
+                a unique key
+
+  @SEQUENCES lists all of the application sequences that are to be
+             replicated.
+
+The values in slon.env are "hardcoded" as far as the tools are
+concerned.
+
+To make this more flexible, slon.env also looks at the environment
+variables SLONYNODES and SLONYSET as alternative sources for
+configuration.
+
+That way, you may do something like:
+
+   for i in `seq 10`; do
+      SLONYNODES="./node$i.config" ./init_cluster.pl
+   done
+
+- Such an "alternative cluster.nodes" might import Pg, and do queries
+against a database to be replicated in order to populate the sets of
+tables and such.
+
+- The "alternative cluster.nodes" might search some sort of 'registry' for
+the set of nodes to be replicated.
+
+Parallel to SLONYNODES is the environment variable SLONYSET, which
+controls the contents of replication sets.  It looks as though it
+should be usual for there to be just one "intentionally active"
+subscription set at any given time, with other sets being set up in
+order to be merged with the "main" set.
+
+Steps to start up replication
+-------------------------------
+
+0.  Dump from source system to destination
+ pg_dump -s -c  flex1 | psql flex2
+  
+1. Initializes the Slony cluster
+  ./init_cluster.pl
+
+  This sets up a FULL cross-join set of paths and listeners, doing
+  something of a shortest-path evaluation of which "store listens" to
+  set up.
+
+2. Start up slon servers for both DB instances
+  ./slon_start.pl node1
+  ./slon_start.pl node2
+
+3. Sets up all the tables for "set 1" for FlexReg 2.0  
+  ./create_set.pl set1
+    
+4. Subscribe Node #2 to Set #1
+  ./subscribe_set.pl set1 node2
+     This is the Big One...
+
+That SHOULD be it, although "should" is probably too strong a word :-)
+
+There are numerous other tools for adding/dropping Slony-I
+configuration, and scripts that might manage simple forms of
+switchover/failover.


More information about the Slony1-commit mailing list