From f0ee0993ee907e19d468a47d674b0d4db5568e50 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Tue, 20 Jan 2015 12:51:16 +0000
Subject: [PATCH] SSDM-1388 handle backup of remote databases. Improve error
 messaging.

SVN: 33287
---
 .../installer/bin/backup-databases.sh         |  30 +++--
 .../installer/bin/chmodx-all-scripts.sh       |   1 +
 .../installer/bin/common-functions.sh         |  11 +-
 .../installer/bin/database-existence-check.sh |   4 +-
 .../installer/bin/finish-installation.sh      |   1 +
 .../izpack/AbstractScriptExecutor.java        |  61 ++++++++--
 .../installer/izpack/ExecuteBackupAction.java |   6 +-
 .../izpack/ExecuteSetupScriptsAction.java     |  22 ++--
 .../izpack/SetDatabasesToBackupAction.java    | 105 +++++++++++++-----
 9 files changed, 173 insertions(+), 68 deletions(-)

diff --git a/installation/resource/installer/bin/backup-databases.sh b/installation/resource/installer/bin/backup-databases.sh
index 7ca81934eb7..f36b6b1ac68 100755
--- a/installation/resource/installer/bin/backup-databases.sh
+++ b/installation/resource/installer/bin/backup-databases.sh
@@ -4,13 +4,14 @@
 #
 # Usage: backup-databases.sh $BACKUP_DIR
 # 
+set -o errexit
 
 #
 # Sets the variable DB_LIST to contain a list of databases 
 # to be backed up.
 # Each line of the function's result has the form :
 #
-# database=XXXX;username=XXXX;password=XXXX
+# database=XXXX;username=XXXX;password=XXXX;host=XXXX
 #
 function listDatabases() {
   
@@ -25,13 +26,19 @@ function listDatabases() {
 # 
 # $1 - a semi-color delimited string of properties (e.g. "key1=value1;key2=value2")
 # $2 - the name of the property (e.g. "key1")
-#
+# $3 - default property value
 #
 function getProperty() {
 
   local properties=$1
   local propName=$2
-  echo $properties | tr ";" "\n" | grep "$propName=" | sed "s/$propName=//"
+  local defaultValue=$3
+  local value=`echo $properties | tr ";" "\n" | grep "$propName=" | sed "s/$propName=//"`
+  if [ "$value" == "" ]; then
+    echo $defaultValue
+  else
+    echo $value
+  fi
 }
 
 function checkForBackup()
@@ -61,12 +68,16 @@ function backupDatabase() {
     return
   fi
 
-  username=$(getProperty $DB_PROPS "username")
-  if [ $(databaseExist $database $username) == "TRUE" ]; then
+  echo "Database description: $DB_PROPS"
+  local hostAndPort=$(getProperty $DB_PROPS "host" "localhost")
+  local host=${hostAndPort%:*} 
+  local port=`if [ "${hostAndPort#*:}" == "$host" ]; then echo 5432; else echo ${hostAndPort#*:}; fi`
+  local username=$(getProperty $DB_PROPS "username")
+  if [ $(databaseExist $host $port $database $username) == "TRUE" ]; then
     local dumpDir=$BACKUP_DIR/$database
   
-    echo "Backing up database $database to $dumpDir..."
-    exe_pg_dump -U $username -Fd $database $PG_DUMP_OPTION -f $dumpDir
+    echo "Backing up database $database@$host:$port (for user $username) to $dumpDir..."
+    exe_pg_dump -U $username -h $host -p $port -Fd $database $PG_DUMP_OPTION -f $dumpDir
   
     if [ "$?" -ne 0 ]; then
       echo "Failed to backup database '$database' !"
@@ -77,7 +88,7 @@ function backupDatabase() {
 
 BASE=`dirname "$0"`
 if [ ${BASE#/} == ${BASE} ]; then
-    BASE="`pwd`/${BASE}"
+    BASE="`pwd`/${BASE}"  
 fi
 source $BASE/common-functions.sh
 
@@ -94,7 +105,7 @@ AS_SERVER=$SERVERS/openBIS-server/
 DSS_SERVER=$SERVERS/datastore_server
 
 listDatabases $AS_SERVER/jetty/etc/service.properties $DSS_SERVER/etc/service.properties
-
+echo "Databases: $DB_LIST"
 PG_DUMP_OPTION=""
 if [[ "`exe_pg_dump --version|awk '{print $3}'`" > "9.2.x" ]]; then
   if [ -f /proc/cpuinfo ]; then
@@ -107,6 +118,7 @@ if [[ "`exe_pg_dump --version|awk '{print $3}'`" > "9.2.x" ]]; then
   echo Database dumping will use $NUMBER_OF_PROCESSORS processors.
   PG_DUMP_OPTION=--jobs=$NUMBER_OF_PROCESSORS
 fi
+echo "dump options: $PG_DUMP_OPtION"
 for DB in $DB_LIST; do
   backupDatabase $DB $PG_DUMP_OPTION
 done
diff --git a/installation/resource/installer/bin/chmodx-all-scripts.sh b/installation/resource/installer/bin/chmodx-all-scripts.sh
index 800f634d58f..72026d62eb2 100644
--- a/installation/resource/installer/bin/chmodx-all-scripts.sh
+++ b/installation/resource/installer/bin/chmodx-all-scripts.sh
@@ -2,6 +2,7 @@
 #
 # Sets the executable flag for all *.sh files within the openBIS installation folder
 #
+set -o errexit
 
 BASE=`dirname "$0"`
 if [ ${BASE#/} == ${BASE} ]; then
diff --git a/installation/resource/installer/bin/common-functions.sh b/installation/resource/installer/bin/common-functions.sh
index 6529ea683f6..7a92084b49d 100644
--- a/installation/resource/installer/bin/common-functions.sh
+++ b/installation/resource/installer/bin/common-functions.sh
@@ -45,20 +45,21 @@ contains()
 #
 # This function should be used as follows:
 #
-# if [ $(databaseExist "openbis_prod" $owner) == "TRUE" ]; then doBackup; fi
+# if [ $(databaseExist localhost 5432 "openbis_prod" $owner) == "TRUE" ]; then doBackup; fi
 #
 databaseExist()
 {
-  local database=$1
-  local owner=$2
-  if [ `exe_psql -U $owner -l | eval "awk '/$database /'" | wc -l` -gt 0 ]; then
+  local host=$1
+  local port=$2
+  local database=$3
+  local owner=$4
+  if [ `exe_psql -U $owner -h $host -p $port -l | eval "awk '/$database /'" | wc -l` -gt 0 ]; then
     echo "TRUE"
   else
     echo "FALSE"
   fi
 }
 
-
 #
 # Run psql command using POSTGRES_BIN path
 #
diff --git a/installation/resource/installer/bin/database-existence-check.sh b/installation/resource/installer/bin/database-existence-check.sh
index f0a4e8b1c67..d791dd32f73 100644
--- a/installation/resource/installer/bin/database-existence-check.sh
+++ b/installation/resource/installer/bin/database-existence-check.sh
@@ -11,5 +11,7 @@ source $BASE/common-functions.sh
 
 DATABASE="$1"
 OWNER="$2"
+HOST=$3
+PORT=$4
 
-databaseExist "$DATABASE" "$OWNER"
\ No newline at end of file
+databaseExist $HOST $PORT "$DATABASE" "$OWNER"
\ No newline at end of file
diff --git a/installation/resource/installer/bin/finish-installation.sh b/installation/resource/installer/bin/finish-installation.sh
index 987f6d9841b..7c5197b67dc 100644
--- a/installation/resource/installer/bin/finish-installation.sh
+++ b/installation/resource/installer/bin/finish-installation.sh
@@ -3,6 +3,7 @@
 # 1) Mark all scripts as executable 
 # 2) Clean up all temporary folders after installation 
 #
+set -o errexit
 
 BASE=`dirname "$0"`
 if [ ${BASE#/} == ${BASE} ]; then
diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/AbstractScriptExecutor.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/AbstractScriptExecutor.java
index 9569913a32e..ec8b22067e5 100644
--- a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/AbstractScriptExecutor.java
+++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/AbstractScriptExecutor.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.installer.izpack;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -23,15 +24,32 @@ import java.io.OutputStream;
 import java.util.Map;
 
 import com.izforge.izpack.api.data.AutomatedInstallData;
+import com.izforge.izpack.api.handler.AbstractUIHandler;
+import com.izforge.izpack.data.PanelAction;
 
 /**
  * Abstract class that can execute admin scripts as part of the installation.
  * 
  * @author Kaloyan Enimanev
  */
-public abstract class AbstractScriptExecutor
+public abstract class AbstractScriptExecutor implements PanelAction
 {
     protected static final String INSTALL_BIN_PATH_VARNAME = "INSTALL_BIN_PATH";
+    
+    @Override
+    public final synchronized void executeAction(AutomatedInstallData data, AbstractUIHandler handler)
+    {
+        try
+        {
+            executeAction(data);
+        } catch (Exception ex)
+        {
+            ex.printStackTrace();
+            handler.emitErrorAndBlockNext("Error", ex.toString());
+        }
+    }
+    
+    protected abstract void executeAction(AutomatedInstallData data);
 
     protected String getAdminScript(AutomatedInstallData data, String scriptFileName)
     {
@@ -60,19 +78,28 @@ public abstract class AbstractScriptExecutor
         }
         try
         {
+            ByteArrayOutputStream stdOutput = new ByteArrayOutputStream();
+            ByteArrayOutputStream errOutput = new ByteArrayOutputStream();
             Process process = pb.start();
-            pipe(process.getErrorStream(), err);
-            pipe(process.getInputStream(), out);
+            pipe(process.getErrorStream(), new DelegatingOutputStream(errOutput, err));
+            pipe(process.getInputStream(), new DelegatingOutputStream(stdOutput, out));
             int returnValue = process.waitFor();
             if (returnValue != 0)
             {
-                System.err.println("Executing of command " + pb.command()
-                        + " has failed. Aborting ...");
-                System.exit(returnValue);
+                System.err.println("Executing of command " + pb.command() + " has failed. Aborting ...");
+                throw new RuntimeException("Executing of command " + pb.command() + " has failed:\n"
+                        + "Exit value: " + returnValue + "\n"
+                        + "-------- Std out ---------\n" + stdOutput + "\n"
+                        + "-------- Err out ---------\n" + errOutput);
             }
         } catch (Exception e)
         {
-            System.out.println("Error executing " + command[0] + ": " + e.getMessage());
+            e.printStackTrace();
+            if (e instanceof RuntimeException)
+            {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException("Error executing " + command[0] + ": " + e.getMessage(), e);
         }
     }
     
@@ -97,5 +124,25 @@ public abstract class AbstractScriptExecutor
             }
             }).start();
     }
+    
+    private static final class DelegatingOutputStream extends OutputStream
+    {
+        private OutputStream[] outputStreams;
+
+        DelegatingOutputStream(OutputStream...outputStreams)
+        {
+            this.outputStreams = outputStreams;
+        }
+
+        @Override
+        public void write(int b) throws IOException
+        {
+            for (OutputStream outputStream : outputStreams)
+            {
+                outputStream.write(b);
+            }
+        }
+        
+    }
 
 }
diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteBackupAction.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteBackupAction.java
index 5e972acc21d..1e99aa77741 100644
--- a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteBackupAction.java
+++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteBackupAction.java
@@ -21,15 +21,13 @@ import java.util.Map;
 
 import com.izforge.izpack.api.data.AutomatedInstallData;
 import com.izforge.izpack.api.data.PanelActionConfiguration;
-import com.izforge.izpack.api.handler.AbstractUIHandler;
-import com.izforge.izpack.data.PanelAction;
 
 /**
  * Executes a script that creates a backup of the existing installation.
  * 
  * @author Kaloyan Enimanev
  */
-public class ExecuteBackupAction extends AbstractScriptExecutor implements PanelAction
+public class ExecuteBackupAction extends AbstractScriptExecutor
 {
     /**
      * a script that creates a installation backup.
@@ -37,7 +35,7 @@ public class ExecuteBackupAction extends AbstractScriptExecutor implements Panel
     private static final String CREATE_BACKUP_SCRIPT = "backup-installation.sh";
 
     @Override
-    public synchronized void executeAction(AutomatedInstallData data, AbstractUIHandler arg1)
+    public synchronized void executeAction(AutomatedInstallData data)
     {
         String script = getAdminScript(data, CREATE_BACKUP_SCRIPT);
         String backupFolder = data.getVariable(GlobalInstallationContext.BACKUP_FOLDER_VARNAME);
diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteSetupScriptsAction.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteSetupScriptsAction.java
index fa550a0332e..13b79c7c96d 100644
--- a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteSetupScriptsAction.java
+++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/ExecuteSetupScriptsAction.java
@@ -28,8 +28,6 @@ import org.apache.commons.io.FileUtils;
 
 import com.izforge.izpack.api.data.AutomatedInstallData;
 import com.izforge.izpack.api.data.PanelActionConfiguration;
-import com.izforge.izpack.api.handler.AbstractUIHandler;
-import com.izforge.izpack.data.PanelAction;
 
 import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
 
@@ -40,7 +38,7 @@ import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel;
  * @author Kaloyan Enimanev
  * @author Franz-Josef Elmer
  */
-public class ExecuteSetupScriptsAction extends AbstractScriptExecutor implements PanelAction
+public class ExecuteSetupScriptsAction extends AbstractScriptExecutor
 {
     static final String DATA_SOURCES_KEY = "data-sources";
     static final String PATHINFO_DB_DATA_SOURCE = "path-info-db";
@@ -66,7 +64,7 @@ public class ExecuteSetupScriptsAction extends AbstractScriptExecutor implements
     private static final String RESTORE_CONFIG_FROM_BACKUP_SCRIPT = "restore-config-from-backup.sh";
 
     @Override
-    public synchronized void executeAction(AutomatedInstallData data, AbstractUIHandler handler)
+    public synchronized void executeAction(AutomatedInstallData data)
     {
         if (GlobalInstallationContext.isFirstTimeInstallation)
         {
@@ -82,17 +80,11 @@ public class ExecuteSetupScriptsAction extends AbstractScriptExecutor implements
         String certificatePassword =
                 data.getVariable(GlobalInstallationContext.KEY_PASSWORD_VARNAME);
         File installDir = GlobalInstallationContext.installDir;
-        try
-        {
-            String pathinfoDBEnabled =
-                    data.getVariable(GlobalInstallationContext.PATHINFO_DB_ENABLED);
-            enablePathinfoDB("false".equalsIgnoreCase(pathinfoDBEnabled) == false, installDir);
-            installKeyStore(keyStoreFileName, installDir);
-            injectPasswords(keyStorePassword, certificatePassword, installDir);
-        } catch (Exception ex)
-        {
-            handler.emitErrorAndBlockNext("Fatal Error", ex.toString());
-        }
+        String pathinfoDBEnabled =
+                data.getVariable(GlobalInstallationContext.PATHINFO_DB_ENABLED);
+        enablePathinfoDB("false".equalsIgnoreCase(pathinfoDBEnabled) == false, installDir);
+        installKeyStore(keyStoreFileName, installDir);
+        injectPasswords(keyStorePassword, certificatePassword, installDir);
     }
 
     void installKeyStore(String keyStoreFileName, File installDir)
diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/SetDatabasesToBackupAction.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/SetDatabasesToBackupAction.java
index 4d35fb8b637..e0b640aa894 100644
--- a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/SetDatabasesToBackupAction.java
+++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/SetDatabasesToBackupAction.java
@@ -27,7 +27,6 @@ import org.apache.commons.io.output.ByteArrayOutputStream;
 
 import com.izforge.izpack.api.data.AutomatedInstallData;
 import com.izforge.izpack.api.data.PanelActionConfiguration;
-import com.izforge.izpack.api.handler.AbstractUIHandler;
 import com.izforge.izpack.data.PanelAction;
 
 import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder;
@@ -49,18 +48,16 @@ public class SetDatabasesToBackupAction extends AbstractScriptExecutor implement
     }
 
     @Override
-    public void executeAction(AutomatedInstallData data, AbstractUIHandler handler)
+    public void executeAction(AutomatedInstallData data)
     {
         try
         {
             String descriptions = extractDescriptions().trim();
             String databases = extractDatabases(data, descriptions);
             data.setVariable(DATABASES_TO_BACKUP_VARNAME, databases);
-            return;
         } catch (Exception ex)
         {
-            ex.printStackTrace();
-            handler.emitError("Exception", ex.toString());
+            throw new RuntimeException("Databse description extraction failed.", ex);
         }
     }
 
@@ -71,23 +68,17 @@ public class SetDatabasesToBackupAction extends AbstractScriptExecutor implement
         {
             for (String description : descriptions.split("\n"))
             {
-                String[] splitted = description.split(";")[0].split("=");
-                if (splitted.length < 2)
+                DatabaseDescription databaseDescription = new DatabaseDescription(description);
+                if (databaseExists(data, databaseDescription))
                 {
-                    throw new IllegalArgumentException("Invalid database description: "
-                            + description);
-                }
-                String database = splitted[1].trim();
-                if (databaseExists(data, database))
-                {
-                    builder.append(database);
+                    builder.append(databaseDescription.getDatabase());
                 }
             }
         }
         return builder.toString();
     }
-
-    private boolean databaseExists(AutomatedInstallData data, String database)
+    
+    private boolean databaseExists(AutomatedInstallData data, DatabaseDescription databaseDescription)
     {
         File scriptFile = getAdminScriptFile(data, "database-existence-check.sh");
         if (scriptFile.exists() == false)
@@ -95,18 +86,13 @@ public class SetDatabasesToBackupAction extends AbstractScriptExecutor implement
             return true;
         }
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
-        String owner =
-                Utils.tryToGetServicePropertyOfAS(GlobalInstallationContext.installDir,
-                        "database.owner");
-        String password =
-                Utils.tryToGetServicePropertyOfAS(GlobalInstallationContext.installDir,
-                        "database.owner-password");
-        
         Map<String, String> env = new HashMap<String, String>();
-        env.put("PGPASSWORD", password);
-        
-        executeAdminScript(env, outputStream, outputStream, scriptFile.getAbsolutePath(), database, owner);
+        env.put("PGPASSWORD", databaseDescription.getPassword());
+        String database = databaseDescription.getDatabase();
+        String owner = databaseDescription.getUsername();
+        String host = databaseDescription.getHost();
+        String port = databaseDescription.getPort();
+        executeAdminScript(env, outputStream, outputStream, scriptFile.getAbsolutePath(), database, owner, host, port);
         return outputStream.toString().trim().equals("FALSE") == false;
 
     }
@@ -134,4 +120,69 @@ public class SetDatabasesToBackupAction extends AbstractScriptExecutor implement
         return new Object[]
             { paths.toArray(new String[0]) };
     }
+    
+    private static final class DatabaseDescription
+    {
+        private final String description;
+        private final String database;
+        private final String username;
+        private final String password;
+        private final String host;
+
+        DatabaseDescription(String description)
+        {
+            this.description = description;
+            String[] parts = description.split(";");
+            if (parts.length < 3)
+            {
+                throw new IllegalArgumentException("Only " + parts.length 
+                        + " parts separated by ';' in database description: " + description);
+            }
+            database = getValue(parts[0]);
+            username = getValue(parts[1]);
+            password = getValue(parts[2]);
+            host = parts.length == 4 ? getValue(parts[3]) : "localhost";
+        }
+        
+        private String getValue(String part)
+        {
+            String[] splitted = part.split("=");
+            return splitted.length == 1 ? "" : splitted[1].trim();
+        }
+
+        public String getDatabase()
+        {
+            return database;
+        }
+
+        public String getUsername()
+        {
+            return username;
+        }
+
+        public String getPassword()
+        {
+            return password;
+        }
+        
+        public String getHost()
+        {
+            int indexOfColon = host.indexOf(':');
+            return indexOfColon < 0 ? host : host.substring(0, indexOfColon);
+        }
+        
+        public String getPort()
+        {
+            int indexOfColon = host.indexOf(':');
+            return indexOfColon < 0 ? "5432" : host.substring(indexOfColon + 1);
+        }
+
+        @Override
+        public String toString()
+        {
+            return description;
+        }
+        
+    }
+
 }
-- 
GitLab