diff --git a/installation/resource/installer/backup-installation.xml b/installation/resource/installer/backup-installation.xml deleted file mode 100644 index f5c947a2e67e085aa2a3a7ea8f2c593a87ef4a93..0000000000000000000000000000000000000000 --- a/installation/resource/installer/backup-installation.xml +++ /dev/null @@ -1,7 +0,0 @@ -<processing> - <job name="Backup existing installation"> - <executefile name="$INSTALL_BIN_PATH/backup-installation.sh"> - <arg>$BACKUP_FOLDER</arg> - </executefile> - </job> -</processing> \ No newline at end of file diff --git a/installation/resource/installer/bin/backup-databases.sh b/installation/resource/installer/bin/backup-databases.sh index 286a595dcf2603c3b0d2ae8945a55b3e152284e7..b02381f22975f44ab3620b5cef19ed32913e3757 100755 --- a/installation/resource/installer/bin/backup-databases.sh +++ b/installation/resource/installer/bin/backup-databases.sh @@ -26,14 +26,12 @@ 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") # -# The result is returned via the variable "propValue" # function getProperty() { local properties=$1 local propName=$2 - propValue=$(echo $properties | tr ";" "\n" | grep "$propName=" | sed "s/$propName=//") - + echo $properties | tr ";" "\n" | grep "$propName=" | sed "s/$propName=//" } # @@ -44,11 +42,12 @@ function backupDatabase() { DB_PROPS=$1 - getProperty $DB_PROPS "database" - database=$propValue - if [ `exe_psql -U postgres -l | eval "awk '/$database /'" | wc -l` -gt 0 ]; then - getProperty $DB_PROPS "username" - username=$propValue + database=$(getProperty $DB_PROPS "database") + if [ $(isEmptyOrContains "$DATABASES_TO_BACKUP" $database) == "FALSE" ]; then + return + fi + if [ $(databaseExist $database) == "TRUE" ]; then + username=$(getProperty $DB_PROPS "username") local dumpFile=$BACKUP_DIR/$database.dmp @@ -73,6 +72,7 @@ if [ "$BACKUP_DIR" == "" ]; then echo ERROR: directory in which configuration should be stored has not been specified! exit 1 fi +DATABASES_TO_BACKUP="$2" SERVERS=$BASE/../servers AS_SERVER=$SERVERS/openBIS-server/ diff --git a/installation/resource/installer/bin/backup-installation.sh b/installation/resource/installer/bin/backup-installation.sh index 309e2da91386e1657962d705552ddbedddf2915e..5bf50b8105424a50266132c192b6e1ff6ced901a 100755 --- a/installation/resource/installer/bin/backup-installation.sh +++ b/installation/resource/installer/bin/backup-installation.sh @@ -21,6 +21,7 @@ if [ "$BACKUP_DIR" == "" ]; then echo ERROR: directory in which configuration should be stored has not been specified! exit 1 fi +DATABASES_TO_BACKUP="$2" $BASE/alldown.sh @@ -31,7 +32,7 @@ echo "Creating backup folder $BACKUP_DIR ..." mkdir -p $CONFIG $BASE/backup-config.sh $CONFIG -$BASE/backup-databases.sh $BACKUP_DIR +$BASE/backup-databases.sh $BACKUP_DIR "$DATABASES_TO_BACKUP" if [ $? -ne "0" ]; then echo "Creating database backups had failed. Aborting ..." exit 1 diff --git a/installation/resource/installer/bin/common-functions.sh b/installation/resource/installer/bin/common-functions.sh index f4ea2032993920c410948c892a0d918b2c0fa159..a5f952a7bc1b35b148270b1a5eba5c33bcdae060 100644 --- a/installation/resource/installer/bin/common-functions.sh +++ b/installation/resource/installer/bin/common-functions.sh @@ -1,5 +1,67 @@ POSTGRES_BIN=`cat $BASE/postgres_bin_path.txt` +# +# Removes white trailing and leading white spaces. +# +# This function should be used as follows: +# +# trimmedString=$(trim $someString) +# +trim() +{ + echo "$1" | sed 's/^ *//g' | sed 's/ *$//g' +} + +# +# Checks whether the first argument, a comma-separated list of items contains the second argument +# as an item. Returns TRUE if this is the case or the list is empty. Trailing and leading +# whitespace of list items are ignored. +# +# This function should be used as follows: +# +# result=$(isEmptyOrContains " abc, def , ghi " "abc") +# +isEmptyOrContains() +{ + if [ "$(trim "$1")" == "" ]; then + echo "TRUE" + return + fi + local list="$1" + local item="$2" + while true; do + local part="${list%%,*}" + local firstItem=$(trim $part) + list="${list#*,}" + if [ "$firstItem" == "$item" ]; then + echo "TRUE" + return + fi + if [ "$part" == "$list" ]; then + echo "FALSE" + return + fi + done +} + +# +# Returns TRUE if the specified database exists. +# +# This function should be used as follows: +# +# if [ $(databaseExist "openbis_prod") == "TRUE" ]; then doBackup; fi +# +databaseExist() +{ + local database=$1 + if [ `exe_psql -U postgres -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 new file mode 100644 index 0000000000000000000000000000000000000000..a38c8f4e7ebcf8e6baacea843f65994a44f33907 --- /dev/null +++ b/installation/resource/installer/bin/database-existence-check.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Checks that a specified database exists. Prints TRUE or FALSE onto the console. +# + +BASE=`dirname "$0"` +if [ ${BASE#/} == ${BASE} ]; then + BASE="`pwd`/${BASE}" +fi +source $BASE/common-functions.sh + +DATABASE="$1" + +databaseExist "$DATABASE" \ No newline at end of file diff --git a/installation/resource/installer/install.xml b/installation/resource/installer/install.xml index d2799fef5b432c12888f46fe40d110ef911f9ab2..eec530ee53aa388dd0e8d27bd8e6395ea842d22f 100644 --- a/installation/resource/installer/install.xml +++ b/installation/resource/installer/install.xml @@ -29,7 +29,6 @@ <res id="TargetPanel.dir" src="@{installer.dist.dir}/default-install-dir.txt" /> <res id="Heading.image" src="@{installer.dist.dir}/openBIS_logo_229x100.png"/> <res id="userInputSpec.xml" src="@{installer.dist.dir}/userInputSpec.xml" /> - <res id="ProcessPanel.Spec.xml" src="@{installer.dist.dir}/backup-installation.xml"/> </resources> <variables> @@ -84,6 +83,20 @@ </java> <returnvalue type="boolean">true</returnvalue> </condition> + <condition type="java" id="isUpdateInstallationWithDatabaseSelection"> + <java> + <class>ch.systemsx.cisd.openbis.installer.izpack.GlobalInstallationContext</class> + <field>isUpdateInstallationWithDatabaseSelection</field> + </java> + <returnvalue type="boolean">true</returnvalue> + </condition> + <condition type="java" id="isUpdateInstallationWithoutDatabaseSelection"> + <java> + <class>ch.systemsx.cisd.openbis.installer.izpack.GlobalInstallationContext</class> + <field>isUpdateInstallationWithoutDatabaseSelection</field> + </java> + <returnvalue type="boolean">true</returnvalue> + </condition> </conditions> <!-- The panels section. We indicate here which panels we want to use. The order will be respected. --> @@ -130,8 +143,15 @@ </actions> </panel> - <panel classname="com.izforge.izpack.panels.userinput.UserInputPanel" id="UserInputPanel.BACKUP" condition="isUpdateInstallation"> + <panel classname="com.izforge.izpack.panels.userinput.UserInputPanel" id="UserInputPanel.BACKUP_OLD" condition="isUpdateInstallationWithoutDatabaseSelection"> + <actions> + <action stage="postvalidate" classname="ch.systemsx.cisd.openbis.installer.izpack.ExecuteBackupAction" /> + </actions> + </panel> + + <panel classname="com.izforge.izpack.panels.userinput.UserInputPanel" id="UserInputPanel.BACKUP" condition="isUpdateInstallationWithDatabaseSelection"> <actions> + <action stage="preactivate" classname="ch.systemsx.cisd.openbis.installer.izpack.SetDatabasesToBackupAction" /> <action stage="postvalidate" classname="ch.systemsx.cisd.openbis.installer.izpack.ExecuteBackupAction" /> </actions> </panel> diff --git a/installation/resource/installer/userInputSpec.xml b/installation/resource/installer/userInputSpec.xml index 716e09525f1f7f05eb26105c3d34d590e0749948..0727dae61c8047bc41d934d248e49a70da5c2680 100644 --- a/installation/resource/installer/userInputSpec.xml +++ b/installation/resource/installer/userInputSpec.xml @@ -1,7 +1,7 @@ <userInput> <panel id="UserInputPanel.DB_CHECK"> <field type="staticText" align="left" - txt="Click on 'Next' to perform the check for the owner and the admin user specified in service.properties." /> + txt="PLease, click next to perform the check for the owner and the admin user specified in service.properties." /> <field type="title" txt="Database access check" bold="true" size="2" /> </panel> @@ -100,9 +100,21 @@ </field> </panel> + <panel id="UserInputPanel.BACKUP_OLD"> + <field type="title" txt="Detected existing openBIS installation" bold="true" size="2" /> + <field type="staticText" align="left" txt="Please, click next to create a backup of the existing installation." /> + </panel> + <panel id="UserInputPanel.BACKUP"> <field type="title" txt="Detected existing openBIS installation" bold="true" size="2" /> + <field type="staticText" align="left" txt="Configuration files and the following databases are backuped:" /> + <field type="text" align="left" variable="DATABASES_TO_BACKUP"> + <spec txt="Databases:" size="39" set="$DATABASES_TO_BACKUP" mustExist="true" allowEmptyValue="false"/> + </field> + <field type="staticText" align="left" txt="Databases can be removed from this list if backup isn't required for them." /> + <field type="space"/> <field type="staticText" align="left" txt="Please, click next to create a backup of the existing installation." /> + </panel> </userInput> \ No newline at end of file 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 d65d09928a68ec0d38522853b559113a33dfaf7a..9569913a32e9b3399c0f36218bc72dcef015452d 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 @@ -19,7 +19,7 @@ package ch.systemsx.cisd.openbis.installer.izpack; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.PrintStream; +import java.io.OutputStream; import java.util.Map; import com.izforge.izpack.api.data.AutomatedInstallData; @@ -35,11 +35,22 @@ public abstract class AbstractScriptExecutor protected String getAdminScript(AutomatedInstallData data, String scriptFileName) { - File adminScriptFile = new File(data.getVariable(INSTALL_BIN_PATH_VARNAME), scriptFileName); + File adminScriptFile = getAdminScriptFile(data, scriptFileName); return adminScriptFile.getAbsolutePath(); } + protected File getAdminScriptFile(AutomatedInstallData data, String scriptFileName) + { + return new File(data.getVariable(INSTALL_BIN_PATH_VARNAME), scriptFileName); + } + protected void executeAdminScript(Map<String, String> customEnv, String... command) + { + executeAdminScript(customEnv, System.out, System.err, command); + } + + protected void executeAdminScript(Map<String, String> customEnv, OutputStream out, + OutputStream err, String... command) { ProcessBuilder pb = new ProcessBuilder(command); pb.environment().putAll(System.getenv()); @@ -50,8 +61,8 @@ public abstract class AbstractScriptExecutor try { Process process = pb.start(); - pipe(process.getErrorStream(), System.err); - pipe(process.getInputStream(), System.out); + pipe(process.getErrorStream(), err); + pipe(process.getInputStream(), out); int returnValue = process.waitFor(); if (returnValue != 0) { @@ -66,7 +77,7 @@ public abstract class AbstractScriptExecutor } - private void pipe(final InputStream src, final PrintStream dest) + private void pipe(final InputStream src, final OutputStream dest) { new Thread(new Runnable() { 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 7415b29ec443d9aa548a29469e57e700ea87a2fe..31f90f58a56a10f5123cd25c4850bb30d9c1d583 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 @@ -38,7 +38,15 @@ public class ExecuteBackupAction extends AbstractScriptExecutor implements Panel { String script = getAdminScript(data, CREATE_BACKUP_SCRIPT); String backupFolder = data.getVariable(GlobalInstallationContext.BACKUP_FOLDER_VARNAME); - executeAdminScript(null, script, backupFolder); + String dataBasesToBackup = data.getVariable(SetDatabasesToBackupAction.DATABASES_TO_BACKUP_VARNAME); + System.out.println("dbs:"+dataBasesToBackup+"<"); + if (dataBasesToBackup == null) + { + executeAdminScript(null, script, backupFolder); + } else + { + executeAdminScript(null, script, backupFolder, dataBasesToBackup); + } } @Override diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/GlobalInstallationContext.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/GlobalInstallationContext.java index b98debcf604a2c26529eb1043e665c7bcc2fad56..9084c7d071fc6444260f8dc714414666910f452c 100644 --- a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/GlobalInstallationContext.java +++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/GlobalInstallationContext.java @@ -58,6 +58,9 @@ public class GlobalInstallationContext */ public static boolean isUpdateInstallation = false; + public static boolean isUpdateInstallationWithoutDatabaseSelection = false; + public static boolean isUpdateInstallationWithDatabaseSelection = false; + /** * set to true if this is the first openBIS installation on the machine. */ @@ -90,6 +93,17 @@ public class GlobalInstallationContext { postgresBinPath = FileUtilities.loadToString(pathFile).trim(); } + String backupScript = + FileUtilities.loadToString(new File(installDir, "bin/backup-installation.sh")); + boolean canBackupDatabasesSelectively = + backupScript.contains(SetDatabasesToBackupAction.DATABASES_TO_BACKUP_VARNAME); + if (canBackupDatabasesSelectively) + { + isUpdateInstallationWithDatabaseSelection = true; + } else + { + isUpdateInstallationWithoutDatabaseSelection = true; + } } data.setVariable(POSTGRES_BIN_VARNAME, postgresBinPath); diff --git a/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/JarClassLoader.java b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/JarClassLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..13ce135a5e06aadedb15c8194adf6eabeac27f4e --- /dev/null +++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/JarClassLoader.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.installer.izpack; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; + +/** + * Class loader based on a JAR file or directory of JAR files + * + * @author Franz-Josef Elmer + */ +public class JarClassLoader extends ClassLoader +{ + private final List<JarFile> jarFiles = new ArrayList<JarFile>(); + private final Map<String, Class<?>> classes = new HashMap<String, Class<?>>(); + + public JarClassLoader(File jarFileOrFolder) + { + super(JarClassLoader.class.getClassLoader()); + addJarFile(jarFileOrFolder); + if (jarFileOrFolder.isDirectory()) + { + File[] files = jarFileOrFolder.listFiles(); + for (File file : files) + { + addJarFile(file); + } + } + } + + private void addJarFile(File file) + { + if (file.isFile() && file.getName().endsWith(".jar")) + { + try + { + jarFiles.add(new JarFile(file)); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + } + + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException + { + return findClass(name); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException + { + Class<?> clazz = classes.get(name); + if (clazz != null) + { + return clazz; + } + for (JarFile jarFile : jarFiles) + { + JarEntry jarEntry = jarFile.getJarEntry(name.replace('.', '/') + ".class"); + if (jarEntry != null) + { + InputStream inputStream = null; + ByteArrayOutputStream outputStream = null; + try + { + inputStream = jarFile.getInputStream(jarEntry); + outputStream = new ByteArrayOutputStream(); + int b = inputStream.read(); + while (b >= 0) + { + outputStream.write(b); + b = inputStream.read(); + } + byte[] bytes = outputStream.toByteArray(); + clazz = defineClass(name, bytes, 0, bytes.length, null); + classes.put(name, clazz); + return clazz; + } catch (Exception ex) + { + // silently ignored, try next + } finally + { + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outputStream); + } + } + } + try + { + Class<?> systemClass = findSystemClass(name); + return systemClass; + } catch (ClassNotFoundException e) + { + return null; + } + } + + + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..9a15ba841aa375cf84628a88b3590ece162a8444 --- /dev/null +++ b/installation/source/java/ch/systemsx/cisd/openbis/installer/izpack/SetDatabasesToBackupAction.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.installer.izpack; + +import java.io.File; +import java.lang.reflect.Method; + +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; + +/** + * @author Franz-Josef Elmer + */ +public class SetDatabasesToBackupAction extends AbstractScriptExecutor implements PanelAction +{ + private static final String HELPER_CLASS = + "ch.systemsx.cisd.openbis.dss.generic.server.dbbackup.BackupDatabaseDescriptionGenerator"; + + + static final String DATABASES_TO_BACKUP_VARNAME = "DATABASES_TO_BACKUP"; + + @Override + public void initialize(PanelActionConfiguration configuration) + { + + } + + @Override + public void executeAction(AutomatedInstallData data, AbstractUIHandler handler) + { + try + { + String descriptions = extractDescriptions(); + String databases = extractDatabases(data, descriptions); + data.setVariable(DATABASES_TO_BACKUP_VARNAME, databases); + return; + } catch (Exception ex) + { + ex.printStackTrace(); + handler.emitError("Exception", ex.toString()); + } + } + + private String extractDatabases(AutomatedInstallData data, String descriptions) + { + CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder(); + for (String description : descriptions.split("\n")) + { + String database = description.split(";")[0].split("=")[1].trim(); + if (databaseExists(data, database)) + { + builder.append(database); + } + } + return builder.toString(); + } + + private boolean databaseExists(AutomatedInstallData data, String database) + { + File scriptFile = getAdminScriptFile(data, "database-existence-check.sh"); + if (scriptFile.exists() == false) + { + return true; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + executeAdminScript(null, outputStream, outputStream, scriptFile.getAbsolutePath(), database); + return outputStream.toString().trim().equals("FALSE") == false; + + } + + private String extractDescriptions() throws Exception + { + Object[] arguments = createArguments(); + File dssLibFolder = new File(GlobalInstallationContext.installDir, Utils.DSS_PATH + "lib"); + Class<?> clazz = new JarClassLoader(dssLibFolder).loadClass(HELPER_CLASS); + Method method = clazz.getMethod("getDescriptions", String[].class); + return (String) method.invoke(null, arguments); + } + + private Object[] createArguments() + { + String asServicePropertiesPath = + new File(GlobalInstallationContext.installDir, Utils.AS_PATH + + Utils.SERVICE_PROPERTIES_PATH).getAbsolutePath(); + String dssServicePropertiesPath = + new File(GlobalInstallationContext.installDir, Utils.DSS_PATH + + Utils.SERVICE_PROPERTIES_PATH).getAbsolutePath(); + return new Object[] + { new String[] + { asServicePropertiesPath, dssServicePropertiesPath } }; + } +}