/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2007-2010 Sun Microsystems, Inc. * Portions copyright 2012 ForgeRock AS. */ package org.opends.quicksetup.upgrader; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import static org.opends.messages.QuickSetupMessages.*; import org.opends.quicksetup.ReturnCode; import org.opends.quicksetup.CliApplication; import org.opends.quicksetup.UserData; import org.opends.quicksetup.UserDataException; import org.opends.quicksetup.ApplicationException; import org.opends.quicksetup.ProgressUpdateListenerDelegate; import org.opends.quicksetup.Installation; import org.opends.quicksetup.Launcher; import org.opends.quicksetup.Status; import org.opends.quicksetup.ProgressStep; import org.opends.quicksetup.BuildInformation; import org.opends.quicksetup.Application; import org.opends.quicksetup.HistoricalRecord; import org.opends.quicksetup.UserInteraction; import org.opends.quicksetup.CliUserInteraction; import static org.opends.quicksetup.Installation.*; import org.opends.quicksetup.event.ProgressUpdateListener; import org.opends.quicksetup.util.ProgressMessageFormatter; import org.opends.quicksetup.util.Utils; import org.opends.quicksetup.util.ServerController; import org.opends.quicksetup.util.FileManager; import org.opends.server.util.cli.CLIException; import org.opends.server.util.cli.Menu; import org.opends.server.util.cli.MenuBuilder; import org.opends.server.util.cli.MenuResult; import java.io.File; import java.io.IOException; import java.io.FilenameFilter; import java.io.FileFilter; import java.util.Arrays; import java.util.Set; import java.util.HashSet; import java.util.EnumSet; import java.util.ArrayList; import java.util.List; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import java.text.DateFormat; /** * Reverts an installation from its current version to a prior version. */ public class Reverter extends Application implements CliApplication { static private final Logger LOG = Logger.getLogger(Reverter.class.getName()); private ReversionProgressStep currentProgressStep = ReversionProgressStep.NOT_STARTED; private ReverterUserData userData; private ProgressUpdateListenerDelegate listenerDelegate; private ApplicationException runError; private ApplicationException runWarning; private Installation installation; private Installation archiveInstallation; private File tempBackupDir; private long historicalOperationId; private BuildInformation fromBuildInfo; private BuildInformation archiveBuildInfo; private boolean abort = false; private boolean restartServer = false; private Stage stage = null; /** * {@inheritDoc} */ public UserData createUserData(Launcher launcher) throws UserDataException, ApplicationException { ReverterUserData ud = null; if (launcher instanceof UpgradeLauncher) { ud = new ReverterUserData(); UpgradeLauncher rl = (UpgradeLauncher)launcher; File filesDir; if (rl.isInteractive()) { if (rl.isNoPrompt()) { StringBuilder sb = new StringBuilder() .append("-") .append(UpgradeLauncher.REVERT_ARCHIVE_OPTION_SHORT) .append("/--") .append(ReversionLauncher.REVERT_ARCHIVE_OPTION_LONG) .append(", -") .append(ReversionLauncher.REVERT_MOST_RECENT_OPTION_SHORT) .append("/--") .append(ReversionLauncher.REVERT_MOST_RECENT_OPTION_LONG); throw new UserDataException(null, INFO_REVERT_ERROR_NO_DIR.get(sb.toString())); } else { CliUserInteraction ui = new CliUserInteraction(); Message[] options = new Message[] { INFO_REVERSION_TYPE_PROMPT_RECENT.get(), INFO_REVERSION_TYPE_PROMPT_FILE.get()}; if (options[0].equals(ui.confirm( INFO_REVERT_CONFIRM_TITLE.get(), INFO_REVERSION_TYPE_PROMPT.get(), INFO_REVERT_CONFIRM_TITLE.get(), UserInteraction.MessageType.QUESTION, options, options[0]))) { ud.setRevertMostRecent(true); } else { ud.setRevertMostRecent(false); // Present a list of reversion archive choices to the user. // In the future perhaps we might also allow them to type // freehand. File historyDir = getInstallation().getHistoryDirectory(); if (historyDir != null && historyDir.exists()) { // Print a wait message, this could take a while System.out.println(formatter.getFormattedWithPoints( INFO_REVERSION_DIR_WAIT.get())); String[] historyChildren = historyDir.list(); Arrays.sort(historyChildren); List raDirList = new ArrayList(); for (int i = historyChildren.length - 1; i >=0; i--) { File raDirCandidate = new File(historyDir, historyChildren[i]); if (isReversionFilesDirectory(raDirCandidate)) { raDirList.add(raDirCandidate); } } File[] raDirs = raDirList.toArray(new File[raDirList.size()]); List raDirChoiceList = new ArrayList(); for (File raDir : raDirs) { String name = raDir.getName(); Message buildInfo = INFO_UPGRADE_BUILD_ID_UNKNOWN.get(); Message date = INFO_GENERAL_UNKNOWN.get(); try { File f = appendFilesDirIfNeccessary(raDir); Installation i = new Installation(f,f); BuildInformation bi = i.getBuildInformation(); buildInfo = Message.raw(bi.toString()); } catch (Exception e) { LOG.log(Level.INFO, "Error determining archive version for " + name); } try { Date d = new Date(Long.valueOf(name)); DateFormat df = DateFormat.getInstance(); date = Message.raw(df.format(d)); } catch (Exception e) { LOG.log(Level.INFO, "Error converting reversion archive " + "name " + name + " to date helper"); } MessageBuilder mb = new MessageBuilder(name); mb.append(" ("); mb.append(INFO_REVERSION_DIR_FROM_UPGRADE.get(buildInfo, date)); mb.append(")"); raDirChoiceList.add(mb.toMessage()); } Message[] raDirChoices = raDirChoiceList.toArray(new Message[0]); if (raDirChoices.length > 0) { MenuBuilder builder = new MenuBuilder(ui); builder.setPrompt(INFO_REVERSION_DIR_PROMPT.get()); for (int i=0; i menu = builder.toMenu(); int resp; try { MenuResult m = menu.run(); if (m.isSuccess()) { resp = m.getValue(); } else { // Should never happen. throw new RuntimeException(); } } catch (CLIException ce) { resp = 1; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } File raDir = raDirs[resp - 1]; raDir = appendFilesDirIfNeccessary(raDir); try { ud.setReversionArchiveDirectory( validateReversionArchiveDirectory(raDir)); } catch (UserDataException ude) { System.err.println(ude.getMessageObject()); } } else { LOG.log(Level.INFO, "No archives in history dir"); throw new UserDataException(null, INFO_REVERT_ERROR_NO_HISTORY_DIR.get()); } } else { LOG.log(Level.INFO, "History dir does not exist"); throw new UserDataException(null, INFO_REVERT_ERROR_NO_HISTORY_DIR.get()); } } } } else if (rl.isRevertMostRecent()) { ud.setRevertMostRecent(true); } else { filesDir = rl.getReversionArchiveDirectory(); filesDir = appendFilesDirIfNeccessary(filesDir); ud.setReversionArchiveDirectory( validateReversionArchiveDirectory(filesDir)); } if (ud.isRevertMostRecent()) { Installation install = getInstallation(); File historyDir = install.getHistoryDirectory(); if (historyDir.exists()) { FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return !HISTORY_LOG_FILE_NAME.equals(name); } }; String[] childNames = historyDir.list(filter); boolean found = false; if (childNames != null && childNames.length > 0) { // The directories beneath 'history' are named according // to the system time at which they were generated. // Go through the directory names in order of most // recent to oldest looking for the first one that // looks like a backup directory Arrays.sort(childNames); for (String childName : childNames) { File b = new File(historyDir, childName); File d = new File(b, HISTORY_BACKUP_FILES_DIR_NAME); if (isReversionFilesDirectory(d)) { found = true; ud.setReversionArchiveDirectory(d); break; } } if (!found) { throw new ApplicationException(ReturnCode.APPLICATION_ERROR, INFO_REVERT_ERROR_INVALID_HISTORY_DIR.get(), null); } } else { throw new ApplicationException(ReturnCode.APPLICATION_ERROR, INFO_REVERT_ERROR_EMPTY_HISTORY_DIR.get(), null); } } else { throw new ApplicationException(ReturnCode.APPLICATION_ERROR, INFO_REVERT_ERROR_NO_HISTORY_DIR.get(), null); } } ud.setQuiet(rl.isQuiet()); ud.setInteractive(!rl.isNoPrompt()); ud.setVerbose(rl.isVerbose()); } return ud; } /** * {@inheritDoc} */ public UserData getUserData() { return this.userData; } /** * {@inheritDoc} */ public void setUserData(UserData userData) { if (userData instanceof ReverterUserData) { this.userData = (ReverterUserData)userData; } } /** * {@inheritDoc} */ public void setProgressMessageFormatter(ProgressMessageFormatter formatter) { this.formatter = formatter; this.listenerDelegate = new ProgressUpdateListenerDelegate(); } /** * {@inheritDoc} */ public ApplicationException getRunError() { return this.runError; } /** * {@inheritDoc} */ public ReturnCode getReturnCode() { return null; } /** * {@inheritDoc} */ public void addProgressUpdateListener(ProgressUpdateListener l) { listenerDelegate.addProgressUpdateListener(l); } /** * {@inheritDoc} */ public void removeProgressUpdateListener(ProgressUpdateListener l) { listenerDelegate.removeProgressUpdateListener(l); } /** * {@inheritDoc} */ public void notifyListeners(Integer ratio, Message currentPhaseSummary, Message newLogDetail) { listenerDelegate.notifyListeners(null, ratio, currentPhaseSummary, newLogDetail); } /** * {@inheritDoc} */ public String getInstallationPath() { String installationPath = null; String path = System.getProperty("INSTALL_ROOT"); if (path != null) { File f = new File(path); if (f.getParentFile() != null && f.getParentFile().getParentFile() != null && new File(f.getParentFile().getParentFile(), LOCKS_PATH_RELATIVE).exists()) { installationPath = Utils.getPath(f.getParentFile().getParentFile()); } else { installationPath = path; } } return installationPath; } /** * {@inheritDoc} */ public String getInstancePath() { String installPath = getInstallationPath() ; return Utils.getInstancePathFromInstallPath(installPath); } /** * {@inheritDoc} */ public ProgressStep getCurrentProgressStep() { return this.currentProgressStep; } /** * {@inheritDoc} */ public Integer getRatio(ProgressStep step) { Integer ratio = null; if (step instanceof ReversionProgressStep) { ratio = ((ReversionProgressStep)step).getProgress(); } return ratio; } /** * {@inheritDoc} */ public Message getSummary(ProgressStep step) { Message txt; if (step == ReversionProgressStep.FINISHED) { txt = getFinalSuccessMessage(); // } else if (step == ReversionProgressStep.FINISHED_CANCELED) { // txt = getFinalCanceledMessage(); // } else if (step == ReversionProgressStep.FINISHED_WITH_ERRORS) { // txt = getFinalErrorMessage(); // } else if (step == ReversionProgressStep.FINISHED_WITH_WARNINGS) { // txt = getFinalWarningMessage(); } else { txt = (((ReversionProgressStep) step).getSummaryMessage()); } return txt; } /** * Returns the progress message for a given progress step. * @param step the progress step. * @return the progress message for the provided step. */ private Message getLogMsg(ReversionProgressStep step) { Message txt; if (step == ReversionProgressStep.FINISHED) { txt = getFinalSuccessMessage(); } else if (step == ReversionProgressStep.FINISHED_CANCELED) { txt = step.getSummaryMessage(); } else if (step == ReversionProgressStep.FINISHED_WITH_ERRORS) { txt = step.getSummaryMessage(); } else if (step == ReversionProgressStep.FINISHED_WITH_WARNINGS) { txt = step.getSummaryMessage(); } else { txt = step.getLogMsg(isVerbose()); } return txt; } /** * {@inheritDoc} */ public boolean isFinished() { return getCurrentProgressStep() == ReversionProgressStep.FINISHED || getCurrentProgressStep() == ReversionProgressStep.FINISHED_WITH_ERRORS || getCurrentProgressStep() == ReversionProgressStep.FINISHED_WITH_WARNINGS || getCurrentProgressStep() == ReversionProgressStep.FINISHED_CANCELED; } /** * {@inheritDoc} */ public boolean isCancellable() { return false; } /** * {@inheritDoc} */ public void cancel() { // not supported } /** * Gets the OpenDS installation associated with the execution of this * command. * @return Installation object representing the current OpenDS installation */ public Installation getInstallation() { if (installation == null) { String installPath = getInstallationPath(); String instancePath = getInstancePath(); if (installPath != null) { if (instancePath != null) { installation = new Installation(installPath, instancePath); } else { installation = new Installation(installPath, installPath); } } } return installation; } /** * {@inheritDoc} */ public void run() { try { // Get the user to confirm if possible UserInteraction ui = userInteraction(); if (ui.isInteractive()) { Message cont = INFO_CONTINUE_BUTTON_LABEL.get(); Message cancel = INFO_CANCEL_BUTTON_LABEL.get(); String toBuildString; BuildInformation toBi = getArchiveBuildInformation(); if (toBi != null) { toBuildString = toBi.toString(); } else { if (getReversionFilesDirectory() == null) { throw new ApplicationException( ReturnCode.APPLICATION_ERROR, INFO_REVERT_ERROR_INVALID_HISTORY_DIR.get(), null); } toBuildString = INFO_UPGRADE_BUILD_ID_UNKNOWN.get().toString(); } if (cancel.equals(ui.confirm( INFO_REVERT_CONFIRM_TITLE.get(), INFO_REVERT_CONFIRM_PROMPT.get( toBuildString, Utils.getPath(getReversionFilesDirectory())), INFO_REVERT_CONFIRM_TITLE.get(), UserInteraction.MessageType.WARNING, new Message[] { cont, cancel }, cont))) { throw new ApplicationException( ReturnCode.CANCELED, INFO_REVERSION_CANCELED.get(), null); } } // Stop the server if necessary. Task note as to whether the server // is running since we will leave it in that state when we are done. Installation installation = getInstallation(); Status status = installation.getStatus(); ServerController sc = new ServerController(installation); if (status.isServerRunning()) { restartServer = true; sc = new ServerController(installation); try { setCurrentProgressStep(ReversionProgressStep.STOPPING_SERVER); LOG.log(Level.INFO, "Stopping server"); if (isVerbose()) { notifyListeners(getTaskSeparator()); } else { notifyListeners(getFormattedWithPoints( INFO_PROGRESS_STOPPING_NON_VERBOSE.get())); } sc.stopServer(!isVerbose()); if (!isVerbose()) { notifyListeners(getFormattedDoneWithLineBreak()); } else { notifyListeners(getLineBreak()); } } catch (ApplicationException ae) { runError = ae; notifyListeners(getFormattedErrorWithLineBreak()); } } try { setCurrentProgressStep(ReversionProgressStep.INITIALIZING); initialize(); notifyListeners(getFormattedDoneWithLineBreak()); } catch (ApplicationException ae) { LOG.log(Level.INFO, "Error initializing reversion", ae); notifyListeners(getFormattedErrorWithLineBreak()); throw ae; } try { LOG.log(Level.INFO, "Reverting components"); setCurrentProgressStep(ReversionProgressStep.REVERTING_FILESYSTEM); revertComponents(); LOG.log(Level.INFO, "Finished reverting components"); notifyListeners(getFormattedDoneWithLineBreak()); } catch (ApplicationException ae) { LOG.log(Level.INFO, "Error reverting components", ae); notifyListeners(getFormattedErrorWithLineBreak()); throw ae; } if (restartServer) { try { LOG.log(Level.INFO, "Restarting server"); setCurrentProgressStep(ReversionProgressStep.STARTING_SERVER); if (isVerbose()) { notifyListeners(getTaskSeparator()); } else { notifyListeners(getFormattedWithPoints( INFO_PROGRESS_STARTING_NON_VERBOSE.get())); } sc.startServer(!isVerbose()); if (!isVerbose()) { notifyListeners(getFormattedDoneWithLineBreak()); } else { notifyListeners(getLineBreak()); } } catch (ApplicationException ae) { runError = ae; notifyListeners(getFormattedErrorWithLineBreak()); } } } catch (Throwable e) { if (!(e instanceof ApplicationException)) { runError = new ApplicationException( ReturnCode.BUG, Message.raw(e.getLocalizedMessage()), e); } else { runError = (ApplicationException)e; abort = ReturnCode.CANCELED.equals( ((ApplicationException)e).getType()); } } finally { end(); } } private void setCurrentProgressStep(ReversionProgressStep step) { this.currentProgressStep = step; int progress = step.getProgress(); Message summary = getSummary(step); Message log = getLogMsg(step); if (step.logRequiresPoints(isVerbose()) && (log != null)) { log = getFormattedWithPoints(log); } notifyListeners(progress, summary, log); } private void initialize() throws ApplicationException { this.historicalOperationId = writeInitialHistoricalRecord( getFromBuildInformation(), getArchiveBuildInformation()); insureRevertability(); backupCurrentInstallation(); } private void backupCurrentInstallation() throws ApplicationException { LOG.log(Level.INFO, "Backing up filesystem"); try { File filesBackupDirectory = getTempBackupDirectory(); FileManager fm = new FileManager(); File root = getInstallation().getRootDirectory(); FileFilter filter = new RevertFileFilter(root); for (String fileName : root.list()) { File f = new File(root, fileName); // Replacing a Windows bat file while it is running with a different // version leads to unpredictable behavior so we make a special case // here and during the upgrade components step. if (Utils.isWindows() && fileName.equals(WINDOWS_UPGRADE_FILE_NAME)) { continue; } fm.move(f, filesBackupDirectory, filter); } LOG.log(Level.INFO, "Finished backing up filesystem"); } catch (ApplicationException ae) { LOG.log(Level.INFO, "Error backing up filesystem", ae); throw ae; } catch (Exception e) { LOG.log(Level.INFO, "Error backing up filesystem", e); throw new ApplicationException( ReturnCode.FILE_SYSTEM_ACCESS_ERROR, INFO_ERROR_BACKUP_FILESYSTEM.get(), e); } } private void revertComponents() throws ApplicationException { try { Stage stage = getStage(); Installation installation = getInstallation(); if (installation.instanceAndInstallInSameDir()) { File root = installation.getRootDirectory(); stage.move(root, new RevertFileFilter(getReversionFilesDirectory())); } else { File root = installation.getRootDirectory(); File revFileRoot = new File(getReversionFilesDirectory(), Installation.HISTORY_BACKUP_FILES_DIR_INSTALL); stage.move(root, new RevertFileFilter(revFileRoot,true),true); root = installation.getInstanceDirectory(); revFileRoot = new File(getReversionFilesDirectory(), Installation.HISTORY_BACKUP_FILES_DIR_INSTANCE); stage.move(root, new RevertFileFilter(revFileRoot,false),false); } // The bits should now be of the new version. Have // the installation update the build information so // that it is correct. LOG.log(Level.INFO, "Reverted bits to " + installation.getBuildInformation(false)); } catch (IOException e) { throw ApplicationException.createFileSystemException( INFO_ERROR_UPGRADING_COMPONENTS.get(), e); } } private File getReversionFilesDirectory() throws ApplicationException, IOException { return userData.getReversionArchiveDirectory(); } private File validateReversionArchiveDirectory(File filesDir) throws UserDataException { if (filesDir == null) { throw new UserDataException(null, INFO_REVERT_ERROR_NULL_FILES_DIR.get()); } else if (!filesDir.isDirectory()) { throw new UserDataException(null, INFO_REVERT_ERROR_NOT_DIR_FILES_DIR.get()); } else if (!isReversionFilesDirectory(filesDir)) { throw new UserDataException(null, INFO_REVERT_ERROR_INVALID_FILES_DIR.get()); } return filesDir; } private boolean isReversionFilesDirectory(File filesDir) { filesDir = appendFilesDirIfNeccessary(filesDir); boolean isFilesDir = false; if (filesDir != null && filesDir.exists() && filesDir.isDirectory()) { String[] children = filesDir.list(); Set cs = new HashSet(Arrays.asList(children)); // TODO: more testing of file dir if (installation.instanceAndInstallInSameDir()) { isFilesDir = cs.contains(CONFIG_PATH_RELATIVE) && cs.contains(LIBRARIES_PATH_RELATIVE); } else { isFilesDir = cs.contains(Installation.HISTORY_BACKUP_FILES_DIR_INSTALL) && cs.contains( Installation.HISTORY_BACKUP_FILES_DIR_INSTANCE); } } return isFilesDir; } private void end() { try { HistoricalRecord.Status status; String note = null; if (runError == null && !abort) { status = HistoricalRecord.Status.SUCCESS; // Since everything went OK, delete the archive LOG.log(Level.INFO, "Cleaning up after reversion"); setCurrentProgressStep(ReversionProgressStep.CLEANUP); deleteArchive(); deleteBackup(); notifyListeners(getFormattedDoneWithLineBreak()); LOG.log(Level.INFO, "Clean up complete"); } else { if (abort) { status = HistoricalRecord.Status.CANCEL; } else { status = HistoricalRecord.Status.FAILURE; note = runError.getLocalizedMessage(); } // Abort the reversion and put things back like we found it LOG.log(Level.INFO, "Canceling reversion"); ProgressStep lastProgressStep = currentProgressStep; setCurrentProgressStep(ReversionProgressStep.ABORT); abort(lastProgressStep); notifyListeners(getFormattedDoneWithLineBreak()); LOG.log(Level.INFO, "Cancelation complete"); } // Remove the lib directory temporarily created by the // launch script with which the program is running deleteReversionLib(); // Write a record in the log file indicating success/failure LOG.log(Level.INFO, "Recording history"); setCurrentProgressStep(ReversionProgressStep.RECORDING_HISTORY); writeHistoricalRecord(historicalOperationId, getFromBuildInformation(), getArchiveBuildInformation(), status, note); notifyListeners(getFormattedDoneWithLineBreak()); LOG.log(Level.INFO, "History recorded"); notifyListeners(getFormattedProgress(INFO_GENERAL_SEE_FOR_HISTORY.get( Utils.getPath(getInstallation().getHistoryLogFile())))); notifyListeners(formatter.getLineBreak()); try { Stage stage = getStage(); List stageMessages = stage.getMessages(); for (Message m : stageMessages) { notifyListeners(m); } } catch (IOException e) { LOG.log(Level.INFO, "failed to access stage", e); } } catch (ApplicationException e) { notifyListeners(getFormattedDoneWithLineBreak()); LOG.log(Level.INFO, "Error cleaning up after reversion.", e); } // Decide final status based on presense of error // WARNING: change this code at your own risk! The ordering // of these statements is important. There are differences // in how the CLI and GUI application's processes exit. // Changing the ordering here may result in messages being // skipped because the process has already exited by the time // processing messages has finished. Need to resolve these // issues. if (abort) { LOG.log(Level.INFO, "reversion canceled by user"); setCurrentProgressStep(ReversionProgressStep.FINISHED_CANCELED); } else if (runError != null) { LOG.log(Level.INFO, "reversion completed with errors", runError); notifyListeners(getFormattedErrorWithLineBreak(runError,true)); notifyListeners(getLineBreak()); setCurrentProgressStep(ReversionProgressStep.FINISHED_WITH_ERRORS); notifyListeners(getLineBreak()); } else if (runWarning != null) { LOG.log(Level.INFO, "reversion completed with warnings"); Message warningText = runWarning.getMessageObject(); // By design, the warnings are written as errors to the details section // as errors. Warning markup is used surrounding the main message // at the end of progress. notifyListeners(getFormattedErrorWithLineBreak(warningText, true)); notifyListeners(getLineBreak()); setCurrentProgressStep(ReversionProgressStep.FINISHED_WITH_WARNINGS); notifyListeners(formatter.getLineBreak()); } else { LOG.log(Level.INFO, "reversion completed successfully"); setCurrentProgressStep(ReversionProgressStep.FINISHED); } } /** * Abort this reversion and repair the installation. * * @param lastStep ProgressStep indicating how much work we will have to * do to get the installation back like we left it * @throws ApplicationException of something goes wrong */ private void abort(ProgressStep lastStep) throws ApplicationException { // This can be used to bypass the aborted reversion cleanup // process so that an autopsy can be performed on the // crippled server. if ("true".equals(System.getProperty(Upgrader.SYS_PROP_NO_ABORT))) { return; } ReversionProgressStep lastReversionStep = (ReversionProgressStep) lastStep; EnumSet stepsStarted = EnumSet.range(ReversionProgressStep.NOT_STARTED, lastReversionStep); if (stepsStarted.contains(ReversionProgressStep.REVERTING_FILESYSTEM)) { // Files were copied from the reversion directory to the current // directory. Repair things by overwriting file in the // root with those that were copied to the backup directory // during revertFiles() File root = getInstallation().getRootDirectory(); File backupDirectory; try { backupDirectory = getTempBackupDirectory(); FileManager fm = new FileManager(); boolean restoreError = false; for (String fileName : backupDirectory.list()) { File f = new File(backupDirectory, fileName); // Do our best to restore the filesystem like // we found it. Just report potential problems // to the user. try { fm.move(f, root, null); } catch (Throwable t) { restoreError = true; notifyListeners(INFO_ERROR_RESTORING_FILE.get(Utils.getPath(f), Utils.getPath(root))); } } if (!restoreError) { fm.deleteRecursively(backupDirectory); } } catch (IOException e) { LOG.log(Level.INFO, "Error getting backup directory", e); } } } /** * Deletes the archived backup to which we reverted. */ private void deleteArchive() { FileManager fm = new FileManager(); try { fm.deleteRecursively(getReversionFilesDirectory()); File parent = getReversionFilesDirectory().getParentFile(); if (Utils.directoryExistsAndIsEmpty(parent.getAbsolutePath())) { fm.deleteRecursively(parent); } } catch (Exception e) { // ignore; this is best effort LOG.log(Level.WARNING, "Error: "+e, e); } } /** * Deletes the backup of the current installation. */ private void deleteBackup() { FileManager fm = new FileManager(); try { fm.deleteRecursively(getTempBackupDirectory()); } catch (Exception e) { // ignore; this is best effort LOG.log(Level.WARNING, "Error: "+e, e); } } /** * Delete the library with which this reversion is currently * running. */ private void deleteReversionLib() { FileManager fm = new FileManager(); try { File tmpDir = getInstallation().getTemporaryDirectory(); File revertLibDir = new File(tmpDir, "revert"); fm.deleteRecursively( revertLibDir, null, FileManager.DeletionPolicy.DELETE_ON_EXIT); } catch (Exception e) { // ignore; this is best effort } } /** * Gets the directory that will be used to store the bits that this * reversion operation will replace. The bits are stored in case there * is a problem with this reversion in which case they can be restored. * * @return File directory where the unreverted bits will be stored. * @throws java.io.IOException if something goes wrong * @throws org.opends.quicksetup.ApplicationException if something goes wrong */ private File getTempBackupDirectory() throws IOException, ApplicationException { if (tempBackupDir == null) { tempBackupDir = new File(getInstallation().getTemporaryDirectory(), HISTORY_BACKUP_FILES_DIR_NAME); if (tempBackupDir.exists()) { FileManager fm = new FileManager(); fm.deleteRecursively(tempBackupDir); } if (!tempBackupDir.mkdirs()) { throw new IOException("error creating files backup directory"); } } return tempBackupDir; } private BuildInformation getFromBuildInformation() { if (fromBuildInfo == null) { if (currentProgressStep.ordinal() < ReversionProgressStep.REVERTING_FILESYSTEM.ordinal()) { try { fromBuildInfo = installation.getBuildInformation(false); } catch (ApplicationException e) { LOG.log(Level.INFO, "Failed to obtain 'from' build information", e); } } } return fromBuildInfo; } private BuildInformation getArchiveBuildInformation() { if (archiveBuildInfo == null) { if (currentProgressStep.ordinal() > ReversionProgressStep.REVERTING_FILESYSTEM.ordinal()) { try { archiveBuildInfo = installation.getBuildInformation(false); } catch (ApplicationException e) { LOG.log(Level.INFO, "Failed to obtain archived build information " + "from reverted files", e); } } else { Installation archiveInstall; try { archiveInstall = getArchiveInstallation(); archiveBuildInfo = archiveInstall.getBuildInformation(); } catch (Exception e) { LOG.log(Level.INFO, "Failed to obtain archived build information " + "from archived files", e); } } } return archiveBuildInfo; } private Message getFinalSuccessMessage() { Message txt; String installPath = Utils.getPath(getInstallation().getRootDirectory()); String newVersion; try { BuildInformation bi = getInstallation().getBuildInformation(); if (bi != null) { newVersion = bi.toString(); } else { newVersion = INFO_UPGRADE_BUILD_ID_UNKNOWN.get().toString(); } } catch (ApplicationException e) { newVersion = INFO_UPGRADE_BUILD_ID_UNKNOWN.get().toString(); } txt = INFO_SUMMARY_REVERT_FINISHED_SUCCESSFULLY_CLI.get( formatter.getFormattedText(Message.raw(installPath)), newVersion); return txt; } /** * Given the current information, determines whether or not * a reversion from the current version to the archived version * is possible. Reversion may not be possible due to 'flag * day' types of changes to the codebase. * @throws org.opends.quicksetup.ApplicationException if revertability * cannot be insured. */ private void insureRevertability() throws ApplicationException { BuildInformation currentVersion; BuildInformation newVersion; try { currentVersion = getInstallation().getBuildInformation(); } catch (ApplicationException e) { LOG.log(Level.INFO, "Error getting build information for " + "current installation", e); throw ApplicationException.createFileSystemException( INFO_ERROR_DETERMINING_CURRENT_BUILD.get(), e); } try { Installation revInstallation = getArchiveInstallation(); newVersion = revInstallation.getBuildInformation(); } catch (ApplicationException ae) { throw ae; } catch (Exception e) { LOG.log(Level.INFO, "Error getting build information for " + "staged installation", e); throw ApplicationException.createFileSystemException( INFO_ERROR_DETERMINING_REVERSION_BUILD.get(), e); } if (currentVersion != null && newVersion != null) { UserInteraction ui = userInteraction(); ReversionIssueNotifier uo = new ReversionIssueNotifier( ui,currentVersion,newVersion); uo.notifyUser(); if (uo.noServerStartFollowingOperation()) { // Some issue dictates that we don't try and restart the server // after this operation. It may be that the databases are no // longer readable after the reversion or something equally earth // shattering. getUserData().setStartServer(false); } } else { LOG.log(Level.INFO, "Did not run reversion issue notifier due to " + "incomplete build information"); } } private Installation getArchiveInstallation() throws ApplicationException, IOException { if (archiveInstallation == null) { File revFiles = getReversionFilesDirectory(); String[] children = revFiles.list(); Set cs = new HashSet(Arrays.asList(children)); if ((cs.contains(Installation.HISTORY_BACKUP_FILES_DIR_INSTALL)) && (cs.contains(Installation.HISTORY_BACKUP_FILES_DIR_INSTANCE))) { File installRevFiles = new File (revFiles,Installation.HISTORY_BACKUP_FILES_DIR_INSTALL); File instanceRevFiles = new File (revFiles,Installation.HISTORY_BACKUP_FILES_DIR_INSTANCE); archiveInstallation = new Installation(installRevFiles,instanceRevFiles); } else { archiveInstallation = new Installation(revFiles,revFiles); } } return archiveInstallation; } private File appendFilesDirIfNeccessary(File archiveDir) { if (archiveDir != null) { // Automatically append the 'filesDir' subdirectory if necessary if (!archiveDir.getName(). endsWith(HISTORY_BACKUP_FILES_DIR_NAME)) { archiveDir = new File(archiveDir, HISTORY_BACKUP_FILES_DIR_NAME); } } return archiveDir; } private Stage getStage() throws IOException, ApplicationException { if (this.stage == null) { this.stage = new Stage(getReversionFilesDirectory()); } return stage; } }