From 75fceea66e311b3de58d76a4c993af0fec13dd3d Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Mon, 27 Apr 2015 13:20:04 +0000
Subject: [PATCH] OPENDJ-1870 CR-6581 Update BackupManager to generic tool
---
opendj-server-legacy/src/messages/org/opends/messages/backend.properties | 11
opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java | 114 +
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java | 708 -----
opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java | 376 +++
opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java | 245 ++
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java | 60
opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java | 8
opendj-server-legacy/src/main/java/org/opends/server/util/BackupManager.java | 1637 +++++++++++++++
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/TracedStorage.java | 51
/dev/null | 1172 ----------
opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java | 883 -------
opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java | 915 -------
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java | 6
opendj-server-legacy/src/main/java/org/opends/server/api/Backupable.java | 126 +
opendj-server-legacy/src/messages/org/opends/messages/utility.properties | 46
opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java | 133 +
opendj-server-legacy/src/messages/org/opends/messages/jeb.properties | 38
17 files changed, 2,947 insertions(+), 3,582 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/Backupable.java b/opendj-server-legacy/src/main/java/org/opends/server/api/Backupable.java
new file mode 100644
index 0000000..844d479
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/Backupable.java
@@ -0,0 +1,126 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2015 ForgeRock AS
+ */
+package org.opends.server.api;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ListIterator;
+
+import org.opends.server.types.DirectoryException;
+
+/**
+ * Represents an entity (storage, backend) that can be backed up.
+ * <p>
+ * The files to backup must be located under a root directory given by
+ * {@code getDirectory()} method. They can be located at any depth level
+ * in a sub-directory. For example, file1, file2 and file3 can be returned as
+ * files to backup:
+ * <pre>
+ * +--- rootDirectory
+ * | \--- file1
+ * | \--- subDirectory
+ * | \--- file2
+ * | \--- file3
+ * </pre>
+ * The {@code getDirectory()} method is also used to provide the root directory used for
+ * the restore of the backup. The actual restore directory depends on the strategy used for
+ * restore, which can be one of these two:
+ * <ul>
+ * <li>Direct restore: the backup is restored directly in the directory provided by {@code getDirectory()} method.
+ * It is the responsibility of the backupable entity to manage saving of current files before the restore, and
+ * to discard them at the end of a successful restore.</li>
+ * <li>Indirect restore: the backup is restored in a temporary directory, derived from the directory provided
+ * by {@code getDirectory()} method (suffixed by "restore-[backupID]"). It is the responsibility of the backupable
+ * entity to switch from the temporary directory to the final one.</li>
+ * </ul>
+ * <p>
+ * The restore strategy is given by {@code isDirectRestore()} method: if {@code true}, it is a direct restore,
+ * otherwise it is an indirect restore.
+ * <p>
+ * Actions taken before and after the restore should be handled in the {@code beforeRestore()} and
+ * {@code afterRestore()} methods.
+ *
+ * @see {@link BackupManager}
+ */
+public interface Backupable
+{
+ /**
+ * Returns the files to backup.
+ *
+ * @return an iterator of files to backup, which may be empty but never {@code null}
+ * @throws DirectoryException
+ * If an error occurs.
+ */
+ ListIterator<Path> getFilesToBackup() throws DirectoryException;
+
+ /**
+ * Returns the directory which acts as the root of all files to backup and restore.
+ *
+ * @return the root directory
+ */
+ File getDirectory();
+
+ /**
+ * Indicates if restore is done directly in the restore directory.
+ *
+ * @return {@code true} if restore is done directly in the restore directory
+ * provided by {@code getDirectory()} method, or {@code false} if restore
+ * is done in a temporary directory.
+ */
+ boolean isDirectRestore();
+
+ /**
+ * Called before the restore operation begins.
+ * <p>
+ * In case of direct restore, the backupable entity should take any action
+ * to save a copy of existing data before restore operation. Saving includes
+ * removing the existing data and copying it in a save directory.
+ *
+ * @return the directory where current files are saved. It may be {@code null}
+ * if not applicable.
+ * @throws DirectoryException
+ * If an error occurs.
+ */
+ Path beforeRestore() throws DirectoryException;
+
+ /**
+ * Called after the restore operation has finished successfully.
+ * <p>
+ * For direct restore, the backupable entity can safely discard the saved copy.
+ * For indirect restore, the backupable entity should switch the restored directory
+ * to the final restore directory.
+ *
+ * @param restoreDirectory
+ * The directory in which files have actually been restored. It is never
+ * {@code null}.
+ * @param saveDirectory
+ * The directory in which current files have been saved. It may be
+ * {@code null} if {@code beforeRestore()} returned {@code null}.
+ * @throws DirectoryException
+ * If an error occurs.
+ */
+ void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException;
+
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
index c6ac617..27a0116 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
@@ -37,33 +37,25 @@
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
+import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.MessageDigest;
+import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Mac;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -80,6 +72,7 @@
import org.opends.server.admin.std.server.SchemaBackendCfg;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.Backend;
+import org.opends.server.api.Backupable;
import org.opends.server.api.ClientConnection;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.AddOperation;
@@ -99,10 +92,12 @@
import org.opends.server.schema.NameFormSyntax;
import org.opends.server.schema.ObjectClassSyntax;
import org.opends.server.types.*;
+import org.opends.server.util.BackupManager;
import org.opends.server.util.DynamicConstants;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
+import org.opends.server.util.StaticUtils;
/**
* This class defines a backend to hold the Directory Server schema information.
@@ -110,10 +105,10 @@
* rather dynamically generates the schema entry whenever it is requested.
*/
public class SchemaBackend extends Backend<SchemaBackendCfg>
- implements ConfigurationChangeListener<SchemaBackendCfg>, AlertGenerator
+ implements ConfigurationChangeListener<SchemaBackendCfg>, AlertGenerator, Backupable
{
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
* The fully-qualified name of this class.
@@ -3948,858 +3943,23 @@
/** {@inheritDoc} */
@Override
- public void createBackup(BackupConfig backupConfig)
- throws DirectoryException
+ public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
- // Get the properties to use for the backup. We don't care whether or not
- // it's incremental, so there's no need to get that.
- String backupID = backupConfig.getBackupID();
- BackupDirectory backupDirectory = backupConfig.getBackupDirectory();
- boolean compress = backupConfig.compressData();
- boolean encrypt = backupConfig.encryptData();
- boolean hash = backupConfig.hashData();
- boolean signHash = backupConfig.signHash();
-
-
- // Create a hash map that will hold the extra backup property information
- // for this backup.
- HashMap<String,String> backupProperties = new HashMap<String,String>();
-
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
- String macKeyID = null;
-
- if (hash)
- {
- if (signHash)
- {
- try
- {
- macKeyID = cryptoManager.getMacEngineKeyEntryID();
- backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
-
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_SCHEMA_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- else
- {
- String digestAlgorithm =
- cryptoManager.getPreferredMessageDigestAlgorithm();
- backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
-
- try
- {
- digest = cryptoManager.getPreferredMessageDigest();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_SCHEMA_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- }
-
-
- // Create an output stream that will be used to write the archive file. At
- // its core, it will be a file output stream to put a file on the disk. If
- // we are to encrypt the data, then that file output stream will be wrapped
- // in a cipher output stream. The resulting output stream will then be
- // wrapped by a zip output stream (which may or may not actually use
- // compression).
- String filename = null;
- OutputStream outputStream;
- try
- {
- filename = SCHEMA_BACKUP_BASE_FILENAME + backupID;
- File archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename);
- if (archiveFile.exists())
- {
- int i=1;
- while (true)
- {
- archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename + "." + i);
- if (archiveFile.exists())
- {
- i++;
- }
- else
- {
- filename = filename + "." + i;
- break;
- }
- }
- }
-
- outputStream = new FileOutputStream(archiveFile, false);
- backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, filename);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_SCHEMA_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
- get(filename, backupDirectory.getPath(), getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // If we should encrypt the data, then wrap the output stream in a cipher
- // output stream.
- if (encrypt)
- {
- try
- {
- outputStream
- = cryptoManager.getCipherOutputStream(outputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_SCHEMA_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file output stream in a zip output stream.
- ZipOutputStream zipStream = new ZipOutputStream(outputStream);
-
- LocalizableMessage message = ERR_SCHEMA_BACKUP_ZIP_COMMENT.get(
- DynamicConstants.PRODUCT_NAME,
- backupID);
- try
- {
- zipStream.setComment(String.valueOf(message));
-
- if (compress)
- {
- zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
- }
- else
- {
- zipStream.setLevel(Deflater.NO_COMPRESSION);
- }
-
- // Create a Comment Entry in the zip file
- // This ensure the backup is never empty, even wher
- // there is no schema file to backup.
-
- String commentName = "schema.comment";
-
- // We'll put the name in the hash, too.
- if (hash)
- {
- if (signHash)
- {
- mac.update(getBytes(commentName));
- } else
- {
- digest.update(getBytes(commentName));
- }
- }
- try
- {
- ZipEntry zipEntry = new ZipEntry(commentName);
- zipStream.putNextEntry(zipEntry);
- zipStream.closeEntry();
- }
- catch (Exception e)
- {
- logger.traceException(e);
- close(zipStream);
-
- message = ERR_SCHEMA_BACKUP_CANNOT_BACKUP_SCHEMA_FILE.get(commentName,
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer
- .getServerErrorResultCode(), message, e);
- }
-
- // Get the path to the directory in which the schema files reside and
- // then get a list of all the files in that directory.
- String schemaInstanceDirPath =
- SchemaConfigManager.getSchemaDirectoryPath();
- File schemaDir;
- File[] schemaFiles = null;
-
- try
- {
- schemaDir = new File(schemaInstanceDirPath);
- schemaFiles = schemaDir.listFiles();
- }
- catch (Exception e)
- {
- // Can't locate or list Instance schema directory
- logger.traceException(e);
-
- message = ERR_SCHEMA_BACKUP_CANNOT_LIST_SCHEMA_FILES.get(
- schemaInstanceDirPath, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- // Iterate through the schema files and write them to the zip stream. If
- // we're using a hash or MAC, then calculate that as well.
- byte[] buffer = new byte[8192];
- String parent = ".instance";
-
- for (File schemaFile : schemaFiles)
- {
- if (backupConfig.isCancelled())
- {
- break;
- }
-
- if (!schemaFile.isFile())
- {
- // If there are any non-file items in the directory (e.g., one or more
- // subdirectories), then we'll skip them.
- continue;
- }
-
- String baseName = schemaFile.getName();
-
- // We'll put the name in the hash, too.
- if (hash)
- {
- if (signHash)
- {
- mac.update(getBytes(baseName + parent));
- } else
- {
- digest.update(getBytes(baseName + parent));
- }
- }
-
- InputStream inputStream = null;
- try
- {
- ZipEntry zipEntry = new ZipEntry(baseName + parent);
- zipStream.putNextEntry(zipEntry);
-
- inputStream = new FileInputStream(schemaFile);
- while (true)
- {
- int bytesRead = inputStream.read(buffer);
- if (bytesRead < 0 || backupConfig.isCancelled())
- {
- break;
- }
-
- if (hash)
- {
- if (signHash)
- {
- mac.update(buffer, 0, bytesRead);
- } else
- {
- digest.update(buffer, 0, bytesRead);
- }
- }
-
- zipStream.write(buffer, 0, bytesRead);
- }
-
- zipStream.closeEntry();
- inputStream.close();
- } catch (Exception e)
- {
- logger.traceException(e);
- close(inputStream, zipStream);
-
- message = ERR_SCHEMA_BACKUP_CANNOT_BACKUP_SCHEMA_FILE.get(baseName,
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer
- .getServerErrorResultCode(), message, e);
- }
- }
-
- }
- finally
- {
- // We're done writing the file, so close the zip stream
- // (which should also close the underlying stream).
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_SCHEMA_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get(
- filename, backupDirectory.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
-
- // Get the digest or MAC bytes if appropriate.
- byte[] digestBytes = null;
- byte[] macBytes = null;
- if (hash)
- {
- if (signHash)
- {
- macBytes = mac.doFinal();
- }
- else
- {
- digestBytes = digest.digest();
- }
- }
-
-
- // Create the backup info structure for this backup and add it to the backup
- // directory.
- // FIXME -- Should I use the date from when I started or finished?
- BackupInfo backupInfo = new BackupInfo(backupDirectory, backupID,
- new Date(), false, compress,
- encrypt, digestBytes, macBytes,
- null, backupProperties);
-
- try
- {
- backupDirectory.addBackup(backupInfo);
- backupDirectory.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_SCHEMA_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
+ new BackupManager(getBackendID()).createBackup(this, backupConfig);
}
/** {@inheritDoc} */
@Override
- public void removeBackup(BackupDirectory backupDirectory,
- String backupID)
- throws DirectoryException
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
- BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message = ERR_BACKUP_MISSING_BACKUPID.get(backupID,
- backupDirectory.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- HashMap<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename =
- backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- File archiveFile = new File(backupDirectory.getPath(), archiveFilename);
-
- try
- {
- backupDirectory.removeBackup(backupID);
- }
- catch (ConfigException e)
- {
- logger.traceException(e);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- e.getMessageObject());
- }
-
- try
- {
- backupDirectory.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the archive file.
- archiveFile.delete();
+ new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
}
/** {@inheritDoc} */
@Override
- public void restoreBackup(RestoreConfig restoreConfig)
- throws DirectoryException
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
- // First, make sure that the requested backup exists.
- BackupDirectory backupDirectory = restoreConfig.getBackupDirectory();
- String backupPath = backupDirectory.getPath();
- String backupID = restoreConfig.getBackupID();
- BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message =
- ERR_SCHEMA_RESTORE_NO_SUCH_BACKUP.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
-
- // Read the backup info structure to determine the name of the file that
- // contains the archive. Then make sure that file exists.
- String backupFilename =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- if (backupFilename == null)
- {
- LocalizableMessage message =
- ERR_SCHEMA_RESTORE_NO_BACKUP_FILE.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- File backupFile = new File(backupPath + File.separator + backupFilename);
- try
- {
- if (! backupFile.exists())
- {
- LocalizableMessage message =
- ERR_SCHEMA_RESTORE_NO_SUCH_FILE.get(backupID, backupFile.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
- catch (DirectoryException de)
- {
- throw de;
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_CHECK_FOR_ARCHIVE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // If the backup is hashed, then we need to get the message digest to use
- // to verify it.
- byte[] unsignedHash = backupInfo.getUnsignedHash();
- MessageDigest digest = null;
- if (unsignedHash != null)
- {
- String digestAlgorithm =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM);
- if (digestAlgorithm == null)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_UNKNOWN_DIGEST.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- digest = DirectoryServer.getCryptoManager().getMessageDigest(
- digestAlgorithm);
- }
- catch (Exception e)
- {
- LocalizableMessage message =
- ERR_SCHEMA_RESTORE_CANNOT_GET_DIGEST.get(backupID, digestAlgorithm);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // If the backup is signed, then we need to get the MAC to use to verify it.
- byte[] signedHash = backupInfo.getSignedHash();
- Mac mac = null;
- if (signedHash != null)
- {
- String macKeyID =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
- if (macKeyID == null)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_UNKNOWN_MAC.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_GET_MAC.get(
- backupID, macKeyID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Create the input stream that will be used to read the backup file. At
- // its core, it will be a file input stream.
- InputStream inputStream;
- try
- {
- inputStream = new FileInputStream(backupFile);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_OPEN_BACKUP_FILE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If the backup is encrypted, then we need to wrap the file input stream
- // in a cipher input stream.
- if (backupInfo.isEncrypted())
- {
- try
- {
- inputStream = DirectoryServer.getCryptoManager()
- .getCipherInputStream(inputStream);
- }
- catch (CryptoManagerException e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_GET_CIPHER.get(
- backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Now wrap the resulting input stream in a zip stream so that we can read
- // its contents. We don't need to worry about whether to use compression or
- // not because it will be handled automatically.
- ZipInputStream zipStream = new ZipInputStream(inputStream);
-
-
- // Determine whether we should actually do the restore, or if we should just
- // try to verify the archive. If we are not going to verify only, then
- // move the current schema directory out of the way so we can keep it around
- // to restore if a problem occurs.
- String schemaInstanceDirPath =
- SchemaConfigManager.getSchemaDirectoryPath();
-
- File schemaInstanceDir = new File(schemaInstanceDirPath);
-
- String backupInstanceDirPath = null;
- File schemaBackupInstanceDir = null;
- boolean verifyOnly = restoreConfig.verifyOnly();
- if (! verifyOnly)
- {
- // Rename the current schema directory if it exists.
- try
- {
- if (schemaInstanceDir.exists())
- {
- String schemaBackupInstanceDirPath = schemaInstanceDirPath + ".save";
- backupInstanceDirPath = schemaBackupInstanceDirPath;
- schemaBackupInstanceDir = new File(backupInstanceDirPath);
- if (schemaBackupInstanceDir.exists())
- {
- int i=2;
- while (true)
- {
- backupInstanceDirPath = schemaBackupInstanceDirPath + i;
- schemaBackupInstanceDir = new File(backupInstanceDirPath);
- if (schemaBackupInstanceDir.exists())
- {
- i++;
- }
- else
- {
- break;
- }
- }
- }
-
- schemaInstanceDir.renameTo(schemaBackupInstanceDir);
- }
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_RENAME_CURRENT_DIRECTORY.
- get(backupID, schemaInstanceDirPath,
- backupInstanceDirPath, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Create a new directory to hold the restored schema files.
- try
- {
- schemaInstanceDir.mkdirs();
- }
- catch (Exception e)
- {
- // Try to restore the previous schema directory if possible. This will
- // probably fail in this case, but try anyway.
- if (schemaBackupInstanceDir != null)
- {
- try
- {
- schemaBackupInstanceDir.renameTo(schemaInstanceDir);
- logger.info(NOTE_SCHEMA_RESTORE_RESTORED_OLD_SCHEMA, schemaInstanceDirPath);
- }
- catch (Exception e2)
- {
- logger.error(ERR_SCHEMA_RESTORE_CANNOT_RESTORE_OLD_SCHEMA, schemaBackupInstanceDir.getPath());
- }
- }
-
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_CREATE_SCHEMA_DIRECTORY.get(
- backupID, schemaInstanceDirPath, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Read through the archive file an entry at a time. For each entry, update
- // the digest or MAC if necessary, and if we're actually doing the restore,
- // then write the files out into the schema directory.
- byte[] buffer = new byte[8192];
- while (true)
- {
- ZipEntry zipEntry;
- try
- {
- zipEntry = zipStream.getNextEntry();
- }
- catch (Exception e)
- {
- // Tell the user where the previous schema was archived.
- if (schemaBackupInstanceDir != null)
- {
- logger.error(ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED, schemaBackupInstanceDir.getPath());
- }
-
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_GET_ZIP_ENTRY.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- if (zipEntry == null)
- {
- break;
- }
-
-
- // Get the filename for the zip entry and update the digest or MAC as
- // necessary.
- String fileName = zipEntry.getName();
- if (digest != null)
- {
- digest.update(getBytes(fileName));
- }
- if (mac != null)
- {
- mac.update(getBytes(fileName));
- }
-
- String baseDirPath = schemaInstanceDirPath;
- Boolean restoreIt = true;
- if (fileName.endsWith(".instance"))
- {
- fileName = fileName.substring(0,fileName.lastIndexOf(".instance"));
- }
- else
- {
- // Skip file.
- // ".install" files are from old backups and should be ignored
- restoreIt = false;
- }
-
- // If we're doing the restore, then create the output stream to write the
- // file.
- OutputStream outputStream = null;
- if (!verifyOnly && restoreIt)
- {
- String filePath = baseDirPath + File.separator + fileName;
- try
- {
- outputStream = new FileOutputStream(filePath);
- }
- catch (Exception e)
- {
- // Tell the user where the previous schema was archived.
- if (schemaBackupInstanceDir != null)
- {
- logger.error(ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED, schemaBackupInstanceDir.getPath());
- }
-
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_CREATE_FILE.get(
- backupID, filePath, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
-
-
- // Read the contents of the file and update the digest or MAC as
- // necessary. If we're actually restoring it, then write it into the
- // new schema directory.
- try
- {
- while (true)
- {
- int bytesRead = zipStream.read(buffer);
- if (bytesRead < 0)
- {
- // We've reached the end of the entry.
- break;
- }
-
-
- // Update the digest or MAC if appropriate.
- if (digest != null)
- {
- digest.update(buffer, 0, bytesRead);
- }
-
- if (mac != null)
- {
- mac.update(buffer, 0, bytesRead);
- }
-
-
- // Write the data to the output stream if appropriate.
- if (outputStream != null)
- {
- outputStream.write(buffer, 0, bytesRead);
- }
- }
-
-
- // We're at the end of the file so close the output stream if we're
- // writing it.
- if (outputStream != null)
- {
- outputStream.close();
- }
- }
- catch (Exception e)
- {
- // Tell the user where the previous schema was archived.
- if (schemaBackupInstanceDir != null)
- {
- logger.error(ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED, schemaBackupInstanceDir.getPath());
- }
-
- LocalizableMessage message = ERR_SCHEMA_RESTORE_CANNOT_PROCESS_ARCHIVE_FILE.get(
- backupID, fileName, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Close the zip stream since we don't need it anymore.
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_SCHEMA_RESTORE_ERROR_ON_ZIP_STREAM_CLOSE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // At this point, we should be done with the contents of the ZIP file and
- // the restore should be complete. If we were generating a digest or MAC,
- // then make sure it checks out.
- if (digest != null)
- {
- byte[] calculatedHash = digest.digest();
- if (Arrays.equals(calculatedHash, unsignedHash))
- {
- logger.info(NOTE_SCHEMA_RESTORE_UNSIGNED_HASH_VALID);
- }
- else
- {
- // Tell the user where the previous schema was archived.
- if (schemaBackupInstanceDir != null)
- {
- logger.error(ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED, schemaBackupInstanceDir.getPath());
- }
-
- LocalizableMessage message =
- ERR_SCHEMA_RESTORE_UNSIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
- if (mac != null)
- {
- byte[] calculatedSignature = mac.doFinal();
- if (Arrays.equals(calculatedSignature, signedHash))
- {
- logger.info(NOTE_SCHEMA_RESTORE_SIGNED_HASH_VALID);
- }
- else
- {
- // Tell the user where the previous schema was archived.
- if (schemaBackupInstanceDir != null)
- {
- logger.error(ERR_SCHEMA_RESTORE_OLD_SCHEMA_SAVED, schemaBackupInstanceDir.getPath());
- }
-
- LocalizableMessage message = ERR_SCHEMA_RESTORE_SIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
-
- // If we are just verifying the archive, then we're done.
- if (verifyOnly)
- {
- logger.info(NOTE_SCHEMA_RESTORE_VERIFY_SUCCESSFUL, backupID, backupPath);
- return;
- }
-
-
- // If we've gotten here, then the archive was restored successfully. Get
- // rid of the temporary copy we made of the previous schema directory and
- // exit.
- if (schemaBackupInstanceDir != null)
- {
- recursiveDelete(schemaBackupInstanceDir);
- }
-
- logger.info(NOTE_SCHEMA_RESTORE_SUCCESSFUL, backupID, backupPath);
+ new BackupManager(getBackendID()).restoreBackup(this, restoreConfig);
}
/** {@inheritDoc} */
@@ -5012,5 +4172,52 @@
public void preloadEntryCache() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Operation not supported.");
}
+
+ /** {@inheritDoc} */
+ @Override
+ public File getDirectory()
+ {
+ return new File(SchemaConfigManager.getSchemaDirectoryPath());
+ }
+
+ private static final FileFilter BACKUP_FILES_FILTER = new FileFilter()
+ {
+ @Override
+ public boolean accept(File file)
+ {
+ return file.getName().endsWith(".ldif");
+ }
+ };
+
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<Path> getFilesToBackup() throws DirectoryException
+ {
+ return BackupManager.getFiles(getDirectory(), BACKUP_FILES_FILTER, getBackendID()).listIterator();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isDirectRestore()
+ {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path beforeRestore() throws DirectoryException
+ {
+ // save current schema files in save directory
+ return BackupManager.saveCurrentFilesToDirectory(this, getBackendID());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
+ {
+ // restore was successful, delete save directory
+ StaticUtils.recursiveDelete(saveDirectory.toFile());
+ }
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
index b1906ab..6454e3e 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackendImpl.java
@@ -27,21 +27,29 @@
package org.opends.server.backends.jeb;
import static com.sleepycat.je.EnvironmentConfig.*;
+
import static org.forgerock.util.Reject.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.JebMessages.*;
+import static org.opends.messages.UtilityMessages.*;
import static org.opends.server.backends.jeb.ConfigurableEnvironment.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
@@ -61,6 +69,7 @@
import org.opends.server.admin.std.server.LocalDBBackendCfg;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.Backend;
+import org.opends.server.api.Backupable;
import org.opends.server.api.DiskSpaceMonitorHandler;
import org.opends.server.api.MonitorProvider;
import org.opends.server.backends.RebuildConfig;
@@ -90,6 +99,7 @@
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.RestoreConfig;
+import org.opends.server.util.BackupManager;
import org.opends.server.util.RuntimeInformation;
import com.sleepycat.je.DatabaseException;
@@ -103,7 +113,7 @@
*/
public class BackendImpl extends Backend<LocalDBBackendCfg>
implements ConfigurationChangeListener<LocalDBBackendCfg>, AlertGenerator,
- DiskSpaceMonitorHandler
+ DiskSpaceMonitorHandler, Backupable
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -246,7 +256,9 @@
cfg.addLocalDBChangeListener(this);
}
- private File getDirectory()
+ /** {@inheritDoc} */
+ @Override
+ public File getDirectory()
{
File parentDirectory = getFileForPath(cfg.getDBDirectory());
return new File(parentDirectory, cfg.getBackendId());
@@ -949,37 +961,230 @@
@Override
public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
- BackupManager backupManager = new BackupManager(getBackendID());
- File parentDir = getFileForPath(cfg.getDBDirectory());
- File backendDir = new File(parentDir, cfg.getBackendId());
- backupManager.createBackup(backendDir, backupConfig);
+ new BackupManager(getBackendID()).createBackup(this, backupConfig);
}
-
-
/** {@inheritDoc} */
@Override
- public void removeBackup(BackupDirectory backupDirectory, String backupID)
- throws DirectoryException
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
- BackupManager backupManager = new BackupManager(getBackendID());
- backupManager.removeBackup(backupDirectory, backupID);
+ new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
}
-
-
/** {@inheritDoc} */
@Override
- public void restoreBackup(RestoreConfig restoreConfig)
- throws DirectoryException
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
- BackupManager backupManager = new BackupManager(getBackendID());
- File parentDir = getFileForPath(cfg.getDBDirectory());
- File backendDir = new File(parentDir, cfg.getBackendId());
- backupManager.restoreBackup(backendDir, restoreConfig);
+ new BackupManager(getBackendID()).restoreBackup(this, restoreConfig);
}
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<Path> getFilesToBackup() throws DirectoryException
+ {
+ return new JELogFilesIterator(getDirectory(), cfg.getBackendId());
+ }
+ /**
+ * Iterator on JE log files to backup.
+ * <p>
+ * The cleaner thread may delete some log files during the backup. The
+ * iterator is automatically renewed if at least one file has been deleted.
+ */
+ static class JELogFilesIterator implements ListIterator<Path>
+ {
+ /** Underlying iterator on files. */
+ private ListIterator<Path> iterator;
+
+ /** Root directory where all files are located. */
+ private final File rootDirectory;
+
+ private final String backendID;
+
+ /** Files to backup. Used to renew the iterator if necessary. */
+ private List<Path> files;
+
+ private String lastFileName = "";
+ private long lastFileSize;
+
+ JELogFilesIterator(File rootDirectory, String backendID) throws DirectoryException
+ {
+ this.rootDirectory = rootDirectory;
+ this.backendID = backendID;
+ setFiles(BackupManager.getFiles(rootDirectory, new JELogFileFilter(), backendID));
+ }
+
+ private void setFiles(List<Path> files) {
+ this.files = files;
+ Collections.sort(files);
+ if (!files.isEmpty())
+ {
+ Path lastFile = files.get(files.size() - 1);
+ lastFileName = lastFile.getFileName().toString();
+ lastFileSize = lastFile.toFile().length();
+ }
+ iterator = files.listIterator();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasNext()
+ {
+ boolean hasNext = iterator.hasNext();
+ if (!hasNext && !files.isEmpty())
+ {
+ try
+ {
+ List<Path> allFiles = BackupManager.getFiles(rootDirectory, new JELogFileFilter(), backendID);
+ List<Path> compare = new ArrayList<Path>(files);
+ compare.removeAll(allFiles);
+ if (!compare.isEmpty())
+ {
+ // at least one file was deleted, the iterator must be renewed based on last file previously available
+ List<Path> newFiles =
+ BackupManager.getFiles(rootDirectory, new JELogFileFilter(lastFileName, lastFileSize), backendID);
+ logger.info(NOTE_JEB_BACKUP_CLEANER_ACTIVITY.get(newFiles.size()));
+ if (!newFiles.isEmpty())
+ {
+ setFiles(newFiles);
+ hasNext = iterator.hasNext();
+ }
+ }
+ }
+ catch (DirectoryException e)
+ {
+ logger.error(ERR_BACKEND_LIST_FILES_TO_BACKUP.get(backendID, stackTraceToSingleLineString(e)));
+ }
+ }
+ return hasNext;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path next()
+ {
+ if (hasNext()) {
+ return iterator.next();
+ }
+ throw new NoSuchElementException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasPrevious()
+ {
+ return iterator.hasPrevious();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path previous()
+ {
+ return iterator.previous();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int nextIndex()
+ {
+ return iterator.nextIndex();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int previousIndex()
+ {
+ return iterator.previousIndex();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException("remove() is not implemented");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void set(Path e)
+ {
+ throw new UnsupportedOperationException("set() is not implemented");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(Path e)
+ {
+ throw new UnsupportedOperationException("add() is not implemented");
+ }
+
+ }
+
+ /**
+ * This class implements a FilenameFilter to detect a JE log file, possibly with a constraint
+ * on the file name and file size.
+ */
+ private static class JELogFileFilter implements FileFilter {
+
+ private final String latestFilename;
+ private final long latestFileSize;
+
+ /**
+ * Creates the filter for log files that are newer than provided file name
+ * or equal to provided file name and of larger size.
+ */
+ JELogFileFilter(String latestFilename, long latestFileSize) {
+ this.latestFilename = latestFilename;
+ this.latestFileSize = latestFileSize;
+ }
+
+ /** Creates the filter for any JE log file. */
+ JELogFileFilter() {
+ this("", 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean accept(File file)
+ {
+ String name = file.getName();
+ int cmp = name.compareTo(latestFilename);
+ return name.endsWith(".jdb") && (cmp > 0 || (cmp == 0 && file.length() > latestFileSize));
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isDirectRestore()
+ {
+ // restore is done in an intermediate directory
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path beforeRestore() throws DirectoryException
+ {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
+ {
+ // intermediate directory content is moved to database directory
+ File targetDirectory = getDirectory();
+ recursiveDelete(targetDirectory);
+ try
+ {
+ Files.move(restoreDirectory, targetDirectory.toPath());
+ }
+ catch(IOException e)
+ {
+ LocalizableMessage msg = ERR_CANNOT_RENAME_RESTORE_DIRECTORY.get(restoreDirectory, targetDirectory.getPath());
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), msg);
+ }
+ }
/** {@inheritDoc} */
@Override
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackupManager.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackupManager.java
deleted file mode 100644
index cc8d01e..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/BackupManager.java
+++ /dev/null
@@ -1,1252 +0,0 @@
-/*
- * 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 legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * 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 legal-notices/CDDLv1_0.txt.
- * 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 2006-2009 Sun Microsystems, Inc.
- * Portions Copyright 2013-2015 ForgeRock AS.
- */
-package org.opends.server.backends.jeb;
-
-import static org.opends.messages.BackendMessages.*;
-import static org.opends.messages.JebMessages.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.security.MessageDigest;
-import java.util.*;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Mac;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.config.server.ConfigException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.*;
-import org.opends.server.util.DynamicConstants;
-import org.opends.server.util.StaticUtils;
-
-/**
- * A backup manager for JE backends.
- */
-public class BackupManager
-{
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
- /**
- * The common prefix for archive files.
- */
- public static final String BACKUP_BASE_FILENAME = "backup-";
-
- /**
- * The name of the property that holds the name of the latest log file
- * at the time the backup was created.
- */
- public static final String PROPERTY_LAST_LOGFILE_NAME = "last_logfile_name";
-
- /**
- * The name of the property that holds the size of the latest log file
- * at the time the backup was created.
- */
- public static final String PROPERTY_LAST_LOGFILE_SIZE = "last_logfile_size";
-
-
- /**
- * The name of the entry in an incremental backup archive file
- * containing a list of log files that are unchanged since the
- * previous backup.
- */
- public static final String ZIPENTRY_UNCHANGED_LOGFILES = "unchanged.txt";
-
- /**
- * The name of a dummy entry in the backup archive file that will act
- * as a placeholder in case a backup is done on an empty backend.
- */
- public static final String ZIPENTRY_EMPTY_PLACEHOLDER = "empty.placeholder";
-
-
- /**
- * The backend ID.
- */
- private String backendID;
-
-
- /**
- * Construct a backup manager for a JE backend.
- * @param backendID The ID of the backend instance for which a backup
- * manager is required.
- */
- public BackupManager(String backendID)
- {
- this.backendID = backendID;
- }
-
- /**
- * Create a backup of the JE backend. The backup is stored in a single zip
- * file in the backup directory. If the backup is incremental, then the
- * first entry in the zip is a text file containing a list of all the JE
- * log files that are unchanged since the previous backup. The remaining
- * zip entries are the JE log files themselves, which, for an incremental,
- * only include those files that have changed.
- * @param backendDir The directory of the backend instance for
- * which the backup is required.
- * @param backupConfig The configuration to use when performing the backup.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- public void createBackup(File backendDir, BackupConfig backupConfig)
- throws DirectoryException
- {
- // Get the properties to use for the backup.
- String backupID = backupConfig.getBackupID();
- BackupDirectory backupDir = backupConfig.getBackupDirectory();
- boolean incremental = backupConfig.isIncremental();
- String incrBaseID = backupConfig.getIncrementalBaseID();
- boolean compress = backupConfig.compressData();
- boolean encrypt = backupConfig.encryptData();
- boolean hash = backupConfig.hashData();
- boolean signHash = backupConfig.signHash();
-
-
- HashMap<String,String> backupProperties = new HashMap<String,String>();
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
- String macKeyID = null;
-
- if (hash)
- {
- if (signHash)
- {
- try
- {
- macKeyID = cryptoManager.getMacEngineKeyEntryID();
- backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
-
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- else
- {
- String digestAlgorithm = cryptoManager
- .getPreferredMessageDigestAlgorithm();
- backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
-
- try
- {
- digest = cryptoManager.getPreferredMessageDigest();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- }
-
-
- // Date the backup.
- Date backupDate = new Date();
-
- // If this is an incremental, determine the base backup for this backup.
- HashSet<String> dependencies = new HashSet<String>();
- BackupInfo baseBackup = null;
-
- if (incremental)
- {
- if (incrBaseID == null && backupDir.getLatestBackup() != null)
- {
- // The default is to use the latest backup as base.
- incrBaseID = backupDir.getLatestBackup().getBackupID();
- }
-
- if (incrBaseID == null)
- {
- // No incremental backup ID: log a message informing that a backup
- // could not be found and that a normal backup will be done.
- incremental = false;
- logger.warn(WARN_BACKUPDB_INCREMENTAL_NOT_FOUND_DOING_NORMAL, backupDir.getPath());
- }
- else
- {
- baseBackup = getBackupInfo(backupDir, incrBaseID);
- }
- }
-
- // Get information about the latest log file from the base backup.
- String latestFileName = null;
- long latestFileSize = 0;
- if (baseBackup != null)
- {
- HashMap<String,String> properties = baseBackup.getBackupProperties();
- latestFileName = properties.get(PROPERTY_LAST_LOGFILE_NAME);
- latestFileSize = Long.parseLong(
- properties.get(PROPERTY_LAST_LOGFILE_SIZE));
- }
-
- /*
- Create an output stream that will be used to write the archive file. At
- its core, it will be a file output stream to put a file on the disk. If
- we are to encrypt the data, then that file output stream will be wrapped
- in a cipher output stream. The resulting output stream will then be
- wrapped by a zip output stream (which may or may not actually use
- compression).
- */
- String archiveFilename = null;
- OutputStream outputStream;
- File archiveFile;
- try
- {
- archiveFilename = BACKUP_BASE_FILENAME + backendID + "-" + backupID;
- archiveFile = new File(backupDir.getPath(), archiveFilename);
- if (archiveFile.exists())
- {
- int i=1;
- while (true)
- {
- archiveFile = new File(backupDir.getPath(),
- archiveFilename + "." + i);
- if (archiveFile.exists())
- {
- i++;
- }
- else
- {
- archiveFilename = archiveFilename + "." + i;
- break;
- }
- }
- }
-
- outputStream = new FileOutputStream(archiveFile, false);
- backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, archiveFilename);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
- get(archiveFilename, backupDir.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If we should encrypt the data, then wrap the output stream in a cipher
- // output stream.
- if (encrypt)
- {
- try
- {
- outputStream
- = cryptoManager.getCipherOutputStream(outputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file output stream in a zip output stream.
- ZipOutputStream zipStream = new ZipOutputStream(outputStream);
-
- LocalizableMessage message = ERR_JEB_BACKUP_ZIP_COMMENT.get(
- DynamicConstants.PRODUCT_NAME,
- backupID, backendID);
- zipStream.setComment(message.toString());
-
- if (compress)
- {
- zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
- }
- else
- {
- zipStream.setLevel(Deflater.NO_COMPRESSION);
- }
-
- // Get a list of all the log files comprising the database.
- FilenameFilter filenameFilter = new FilenameFilter()
- {
- @Override
- public boolean accept(File d, String name)
- {
- return name.endsWith(".jdb");
- }
- };
-
- File[] logFiles;
- try
- {
- logFiles = backendDir.listFiles(filenameFilter);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- if (logFiles == null)
- {
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
- }
-
- // Check to see if backend is empty. If so, insert placeholder entry into
- // archive
- if(logFiles.length <= 0)
- {
- try
- {
- ZipEntry emptyPlaceholder = new ZipEntry(ZIPENTRY_EMPTY_PLACEHOLDER);
- zipStream.putNextEntry(emptyPlaceholder);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- ZIPENTRY_EMPTY_PLACEHOLDER, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
-
- // Sort the log files from oldest to youngest since this is the order
- // in which they must be copied.
- // This is easy since the files are created in alphabetical order by JE.
- Arrays.sort(logFiles);
-
- try
- {
- // Process log files that are unchanged from the base backup.
- int indexCurrent = 0;
- if (latestFileName != null)
- {
- ArrayList<String> unchangedList = new ArrayList<String>();
- while (indexCurrent < logFiles.length &&
- !backupConfig.isCancelled())
- {
- File logFile = logFiles[indexCurrent];
- String logFileName = logFile.getName();
-
- // Stop when we get to the first log file that has been
- // written since the base backup.
- int compareResult = logFileName.compareTo(latestFileName);
- if (compareResult > 0 ||
- (compareResult == 0 && logFile.length() != latestFileSize))
- {
- break;
- }
-
- logger.info(NOTE_JEB_BACKUP_FILE_UNCHANGED, logFileName);
-
- unchangedList.add(logFileName);
-
- indexCurrent++;
- }
-
- // Write a file containing the list of unchanged log files.
- if (!unchangedList.isEmpty())
- {
- String zipEntryName = ZIPENTRY_UNCHANGED_LOGFILES;
- try
- {
- archiveList(zipStream, mac, digest, zipEntryName, unchangedList);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- zipEntryName, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- // Set the dependency.
- dependencies.add(baseBackup.getBackupID());
- }
- }
-
- // Write the new log files to the zip file.
- do
- {
- boolean deletedFiles = false;
-
- while (indexCurrent < logFiles.length &&
- !backupConfig.isCancelled())
- {
- File logFile = logFiles[indexCurrent];
-
- try
- {
- latestFileSize = archiveFile(zipStream, mac, digest,
- logFile, backupConfig);
- latestFileName = logFile.getName();
- }
- catch (FileNotFoundException e)
- {
- logger.traceException(e);
-
- // A log file has been deleted by the cleaner since we started.
- deletedFiles = true;
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- logFile.getName(), stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- indexCurrent++;
- }
-
- if (deletedFiles)
- {
- /*
- The cleaner is active and has deleted one or more of the log files
- since we started. The in-use data from those log files will have
- been written to new log files, so we must include those new files.
- */
- final String latest = logFiles[logFiles.length-1].getName();
- FilenameFilter filter = new JELatestFileFilter(latest,
- latestFileSize);
-
- try
- {
- logFiles = backendDir.listFiles(filter);
- indexCurrent = 0;
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- if (logFiles == null)
- {
- break;
- }
-
- Arrays.sort(logFiles);
-
- logger.info(NOTE_JEB_BACKUP_CLEANER_ACTIVITY, logFiles.length);
- }
- else
- {
- // We are done.
- break;
- }
- }
- while (true);
-
- }
- // FIXME: The handling of exception below, plus the lack of finally block
- // to close the zipStream is clumsy. Needs cleanup and best practice.
- catch (DirectoryException e)
- {
- logger.traceException(e);
-
- try
- {
- zipStream.close();
- } catch (Exception e2) {}
- }
-
- // We're done writing the file, so close the zip stream (which should also
- // close the underlying stream).
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_CLOSE_ZIP_STREAM.
- get(archiveFilename, backupDir.getPath(),
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Get the digest or MAC bytes if appropriate.
- byte[] digestBytes = null;
- byte[] macBytes = null;
- if (hash)
- {
- if (signHash)
- {
- macBytes = mac.doFinal();
- }
- else
- {
- digestBytes = digest.digest();
- }
- }
-
-
- // Create a descriptor for this backup.
- backupProperties.put(PROPERTY_LAST_LOGFILE_NAME, latestFileName);
- backupProperties.put(PROPERTY_LAST_LOGFILE_SIZE,
- String.valueOf(latestFileSize));
- BackupInfo backupInfo = new BackupInfo(backupDir, backupID,
- backupDate, incremental, compress,
- encrypt, digestBytes, macBytes,
- dependencies, backupProperties);
-
- try
- {
- backupDir.addBackup(backupInfo);
- backupDir.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDir.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the backup if this operation was cancelled since the
- // backup may be incomplete
- if (backupConfig.isCancelled())
- {
- removeBackup(backupDir, backupID);
- }
- }
-
-
-
- /**
- * Restore a JE backend from backup, or verify the backup.
- * @param backendDir The configuration of the backend instance to be
- * restored.
- * @param restoreConfig The configuration to use when performing the restore.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- public void restoreBackup(File backendDir,
- RestoreConfig restoreConfig)
- throws DirectoryException
- {
- // Get the properties to use for the restore.
- String backupID = restoreConfig.getBackupID();
- BackupDirectory backupDir = restoreConfig.getBackupDirectory();
- boolean verifyOnly = restoreConfig.verifyOnly();
-
- BackupInfo backupInfo = getBackupInfo(backupDir, backupID);
-
- // Create a restore directory with a different name to the backend
- // directory.
- File restoreDir = new File(backendDir.getPath() + "-restore-" + backupID);
- if (!verifyOnly)
- {
- // FIXME: It's odd that we try to clean the directory before creating it
- cleanup(restoreDir);
- restoreDir.mkdir();
- }
-
- // Get the set of restore files that are in dependencies.
- Set<String> includeFiles;
- try
- {
- includeFiles = getUnchanged(backupDir, backupInfo);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_RESTORE.get(
- backupInfo.getBackupID(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Restore any dependencies.
- List<BackupInfo> dependents = getDependents(backupDir, backupInfo);
- for (BackupInfo dependent : dependents)
- {
- try
- {
- restoreArchive(restoreDir, restoreConfig, dependent, includeFiles);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_RESTORE.get(
- dependent.getBackupID(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Restore the final archive file.
- try
- {
- restoreArchive(restoreDir, restoreConfig, backupInfo, null);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_RESTORE.get(
- backupInfo.getBackupID(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Delete the current backend directory and rename the restore directory.
- if (!verifyOnly)
- {
- StaticUtils.recursiveDelete(backendDir);
- if (!restoreDir.renameTo(backendDir))
- {
- LocalizableMessage msg = ERR_JEB_CANNOT_RENAME_RESTORE_DIRECTORY.get(
- restoreDir.getPath(), backendDir.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- msg);
- }
- }
- }
-
- private void cleanup(File directory) {
- File[] files = directory.listFiles();
- if (files != null)
- {
- for (File f : files)
- {
- f.delete();
- }
- }
- }
-
- /**
- * Removes the specified backup if it is possible to do so.
- *
- * @param backupDir The backup directory structure with which the
- * specified backup is associated.
- * @param backupID The backup ID for the backup to be removed.
- *
- * @throws DirectoryException If it is not possible to remove the specified
- * backup for some reason (e.g., no such backup
- * exists or there are other backups that are
- * dependent upon it).
- */
- public void removeBackup(BackupDirectory backupDir,
- String backupID)
- throws DirectoryException
- {
- // Keep information about backup to be deleted for file removal
- BackupInfo backupInfo = getBackupInfo(backupDir, backupID);
-
- try
- {
- backupDir.removeBackup(backupID);
- }
- catch (ConfigException e)
- {
- logger.traceException(e);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- e.getMessageObject());
- }
-
- try
- {
- backupDir.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDir.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the archive file.
- File archiveFile = getArchiveFile(backupDir, backupInfo);
- archiveFile.delete();
-
- }
-
- private File getArchiveFile(BackupDirectory backupDir,
- BackupInfo backupInfo) {
- Map<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename =
- backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- return new File(backupDir.getPath(), archiveFilename);
- }
-
-
- /**
- * Restore the contents of an archive file. If the archive is being
- * restored as a dependency, then only files in the specified set
- * are restored, and the restored files are removed from the set. Otherwise
- * all files from the archive are restored, and files that are to be found
- * in dependencies are added to the set.
- *
- * @param restoreDir The directory in which files are to be restored.
- * @param restoreConfig The restore configuration.
- * @param backupInfo The backup containing the files to be restored.
- * @param includeFiles The set of files to be restored. If null, then
- * all files are restored.
- * @throws DirectoryException If a Directory Server error occurs.
- * @throws IOException If an I/O exception occurs during the restore.
- */
- private void restoreArchive(File restoreDir,
- RestoreConfig restoreConfig,
- BackupInfo backupInfo,
- Set<String> includeFiles)
- throws DirectoryException,IOException
- {
- BackupDirectory backupDir = restoreConfig.getBackupDirectory();
- boolean verifyOnly = restoreConfig.verifyOnly();
-
- String backupID = backupInfo.getBackupID();
- boolean encrypt = backupInfo.isEncrypted();
- byte[] hash = backupInfo.getUnsignedHash();
- byte[] signHash = backupInfo.getSignedHash();
-
- HashMap<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename =
- backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- File archiveFile = new File(backupDir.getPath(), archiveFilename);
-
- InputStream inputStream = new FileInputStream(archiveFile);
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
-
- if (signHash != null)
- {
- String macKeyID = backupProperties.get(BACKUP_PROPERTY_MAC_KEY_ID);
-
- try
- {
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- if (hash != null)
- {
- String digestAlgorithm = backupProperties.get(
- BACKUP_PROPERTY_DIGEST_ALGORITHM);
-
- try
- {
- digest = cryptoManager.getMessageDigest(digestAlgorithm);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // If the data is encrypted, then wrap the input stream in a cipher
- // input stream.
- if (encrypt)
- {
- try
- {
- inputStream = cryptoManager.getCipherInputStream(inputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file input stream in a zip input stream.
- ZipInputStream zipStream = new ZipInputStream(inputStream);
-
- // Iterate through the entries in the zip file.
- ZipEntry zipEntry = zipStream.getNextEntry();
- while (zipEntry != null && !restoreConfig.isCancelled())
- {
- String name = zipEntry.getName();
-
- if (name.equals(ZIPENTRY_EMPTY_PLACEHOLDER))
- {
- // This entry is treated specially to indicate a backup of an empty
- // backend was attempted.
-
- zipEntry = zipStream.getNextEntry();
- continue;
- }
-
- if (name.equals(ZIPENTRY_UNCHANGED_LOGFILES))
- {
- // This entry is treated specially. It is never restored,
- // and its hash is computed on the strings, not the bytes.
- if (mac != null || digest != null)
- {
- // The file name is part of the hash.
- if (mac != null)
- {
- mac.update(getBytes(name));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(name));
- }
-
- InputStreamReader reader = new InputStreamReader(zipStream);
- BufferedReader bufferedReader = new BufferedReader(reader);
- String line = bufferedReader.readLine();
- while (line != null)
- {
- if (mac != null)
- {
- mac.update(getBytes(line));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(line));
- }
-
- line = bufferedReader.readLine();
- }
- }
-
- zipEntry = zipStream.getNextEntry();
- continue;
- }
-
- // See if we need to restore the file.
- File file = new File(restoreDir, name);
- OutputStream outputStream = null;
- if ((includeFiles == null || includeFiles.contains(zipEntry.getName()))
- && !verifyOnly)
- {
- outputStream = new FileOutputStream(file);
- }
-
- if (outputStream != null || mac != null || digest != null)
- {
- if (verifyOnly)
- {
- logger.info(NOTE_JEB_BACKUP_VERIFY_FILE, zipEntry.getName());
- }
-
- // The file name is part of the hash.
- if (mac != null)
- {
- mac.update(getBytes(name));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(name));
- }
-
- // Process the file.
- long totalBytesRead = 0;
- byte[] buffer = new byte[8192];
- int bytesRead = zipStream.read(buffer);
- while (bytesRead > 0 && !restoreConfig.isCancelled())
- {
- totalBytesRead += bytesRead;
-
- if (mac != null)
- {
- mac.update(buffer, 0, bytesRead);
- }
-
- if (digest != null)
- {
- digest.update(buffer, 0, bytesRead);
- }
-
- if (outputStream != null)
- {
- outputStream.write(buffer, 0, bytesRead);
- }
-
- bytesRead = zipStream.read(buffer);
- }
-
- if (outputStream != null)
- {
- outputStream.close();
-
- logger.info(NOTE_JEB_BACKUP_RESTORED_FILE, zipEntry.getName(), totalBytesRead);
- }
- }
-
- zipEntry = zipStream.getNextEntry();
- }
-
- zipStream.close();
-
- // Check the hash.
- if (digest != null && !Arrays.equals(digest.digest(), hash))
- {
- LocalizableMessage message = ERR_JEB_BACKUP_UNSIGNED_HASH_ERROR.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
- }
-
- if (mac != null)
- {
- byte[] computedSignHash = mac.doFinal();
-
- if (!Arrays.equals(computedSignHash, signHash))
- {
- LocalizableMessage message = ERR_JEB_BACKUP_SIGNED_HASH_ERROR.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
- }
-
-
-
- /**
- * Writes a file to an entry in the archive file.
- * @param zipStream The zip output stream to which the file is to be
- * written.
- * @param mac A message authentication code to be updated, if not null.
- * @param digest A message digest to be updated, if not null.
- * @param file The file to be written.
- * @return The number of bytes written from the file.
- * @throws FileNotFoundException If the file to be archived does not exist.
- * @throws IOException If an I/O error occurs while archiving the file.
- */
- private long archiveFile(ZipOutputStream zipStream,
- Mac mac, MessageDigest digest, File file,
- BackupConfig backupConfig)
- throws IOException, FileNotFoundException
- {
- ZipEntry zipEntry = new ZipEntry(file.getName());
-
- // Open the file for reading.
- InputStream inputStream = new FileInputStream(file);
-
- // Start the zip entry.
- zipStream.putNextEntry(zipEntry);
-
- // Put the name in the hash.
- if (mac != null)
- {
- mac.update(getBytes(file.getName()));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(file.getName()));
- }
-
- // Write the file.
- long totalBytesRead = 0;
- byte[] buffer = new byte[8192];
- int bytesRead = inputStream.read(buffer);
- while (bytesRead > 0 && !backupConfig.isCancelled())
- {
- if (mac != null)
- {
- mac.update(buffer, 0, bytesRead);
- }
-
- if (digest != null)
- {
- digest.update(buffer, 0, bytesRead);
- }
-
- zipStream.write(buffer, 0, bytesRead);
- totalBytesRead += bytesRead;
- bytesRead = inputStream.read(buffer);
- }
- inputStream.close();
-
- // Finish the zip entry.
- zipStream.closeEntry();
-
- logger.info(NOTE_JEB_BACKUP_ARCHIVED_FILE, zipEntry.getName());
-
- return totalBytesRead;
- }
-
- /**
- * Write a list of strings to an entry in the archive file.
- * @param zipStream The zip output stream to which the entry is to be
- * written.
- * @param mac An optional MAC to be updated.
- * @param digest An optional message digest to be updated.
- * @param fileName The name of the zip entry to be written.
- * @param list A list of strings to be written. The strings must not
- * contain newlines.
- * @throws IOException If an I/O error occurs while writing the archive entry.
- */
- private void archiveList(ZipOutputStream zipStream,
- Mac mac, MessageDigest digest, String fileName,
- List<String> list)
- throws IOException
- {
- ZipEntry zipEntry = new ZipEntry(fileName);
-
- // Start the zip entry.
- zipStream.putNextEntry(zipEntry);
-
- // Put the name in the hash.
- if (mac != null)
- {
- mac.update(getBytes(fileName));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(fileName));
- }
-
- Writer writer = new OutputStreamWriter(zipStream);
- for (String s : list)
- {
- if (mac != null)
- {
- mac.update(getBytes(s));
- }
-
- if (digest != null)
- {
- digest.update(getBytes(s));
- }
-
- writer.write(s);
- writer.write(EOL);
- }
- writer.flush();
-
- // Finish the zip entry.
- zipStream.closeEntry();
- }
-
- /**
- * Obtains the set of files in a backup that are unchanged from its
- * dependent backup or backups. This list is stored as the first entry
- * in the archive file.
- * @param backupDir The backup directory.
- * @param backupInfo The backup info.
- * @return The set of files that were unchanged.
- * @throws DirectoryException If an error occurs while trying to get the
- * appropriate cipher algorithm for an encrypted backup.
- * @throws IOException If an I/O error occurs while reading the backup
- * archive file.
- */
- private Set<String> getUnchanged(BackupDirectory backupDir,
- BackupInfo backupInfo)
- throws DirectoryException, IOException
- {
- HashSet<String> hashSet = new HashSet<String>();
-
- boolean encrypt = backupInfo.isEncrypted();
-
- File archiveFile = getArchiveFile(backupDir, backupInfo);
-
- InputStream inputStream = new FileInputStream(archiveFile);
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
-
- // If the data is encrypted, then wrap the input stream in a cipher
- // input stream.
- if (encrypt)
- {
- try
- {
- inputStream = cryptoManager.getCipherInputStream(inputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file input stream in a zip input stream.
- ZipInputStream zipStream = new ZipInputStream(inputStream);
-
- // Iterate through the entries in the zip file.
- ZipEntry zipEntry = zipStream.getNextEntry();
- while (zipEntry != null)
- {
- // We are looking for the entry containing the list of unchanged files.
- if (zipEntry.getName().equals(ZIPENTRY_UNCHANGED_LOGFILES))
- {
- InputStreamReader reader = new InputStreamReader(zipStream);
- BufferedReader bufferedReader = new BufferedReader(reader);
- String line = bufferedReader.readLine();
- while (line != null)
- {
- hashSet.add(line);
- line = bufferedReader.readLine();
- }
- break;
- }
-
- zipEntry = zipStream.getNextEntry();
- }
-
- zipStream.close();
- return hashSet;
- }
-
- /**
- * Obtains a list of the dependencies of a given backup in order from
- * the oldest (the full backup), to the most recent.
- * @param backupDir The backup directory.
- * @param backupInfo The backup for which dependencies are required.
- * @return A list of dependent backups.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- private ArrayList<BackupInfo> getDependents(BackupDirectory backupDir,
- BackupInfo backupInfo)
- throws DirectoryException
- {
- ArrayList<BackupInfo> dependents = new ArrayList<BackupInfo>();
- while (backupInfo != null && !backupInfo.getDependencies().isEmpty())
- {
- String backupID = backupInfo.getDependencies().iterator().next();
- backupInfo = getBackupInfo(backupDir, backupID);
- if (backupInfo != null)
- {
- dependents.add(backupInfo);
- }
- }
- Collections.reverse(dependents);
- return dependents;
- }
-
- /**
- * Get the information for a given backup ID from the backup directory.
- * @param backupDir The backup directory.
- * @param backupID The backup ID.
- * @return The backup information, never null.
- * @throws DirectoryException If the backup information cannot be found.
- */
- private BackupInfo getBackupInfo(BackupDirectory backupDir,
- String backupID) throws DirectoryException
- {
- BackupInfo backupInfo = backupDir.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message = ERR_BACKUP_MISSING_BACKUPID.get(backupID, backupDir.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
- }
- return backupInfo;
- }
-
- /**
- * This class implements a FilenameFilter to detect the last file
- * from a JE database.
- */
- private static class JELatestFileFilter implements FilenameFilter {
- private final String latest;
- private final long latestSize;
-
- public JELatestFileFilter(String latest, long latestSize) {
- this.latest = latest;
- this.latestSize = latestSize;
- }
-
- @Override
- public boolean accept(File d, String name)
- {
- if (!name.endsWith(".jdb")) return false;
- int compareTo = name.compareTo(latest);
- return compareTo > 0 || compareTo == 0 && d.length() > latestSize;
- }
- }
-}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
index 94d05ce..7a9b8f7 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/persistit/PersistItStorage.java
@@ -31,12 +31,19 @@
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.JebMessages.*;
+import static org.opends.messages.UtilityMessages.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
-import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -48,6 +55,7 @@
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.PersistitBackendCfg;
+import org.opends.server.api.Backupable;
import org.opends.server.api.DiskSpaceMonitorHandler;
import org.opends.server.backends.pluggable.spi.Cursor;
import org.opends.server.backends.pluggable.spi.Importer;
@@ -63,7 +71,12 @@
import org.opends.server.core.MemoryQuota;
import org.opends.server.core.ServerContext;
import org.opends.server.extensions.DiskSpaceMonitor;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DirectoryException;
import org.opends.server.types.FilePermission;
+import org.opends.server.types.RestoreConfig;
+import org.opends.server.util.BackupManager;
import com.persistit.Configuration;
import com.persistit.Configuration.BufferPoolConfiguration;
@@ -81,7 +94,7 @@
/** PersistIt database implementation of the {@link Storage} engine. */
@SuppressWarnings("javadoc")
-public final class PersistItStorage implements Storage, ConfigurationChangeListener<PersistitBackendCfg>,
+public final class PersistItStorage implements Storage, Backupable, ConfigurationChangeListener<PersistitBackendCfg>,
DiskSpaceMonitorHandler
{
private static final String VOLUME_NAME = "dj";
@@ -746,18 +759,120 @@
return new File(parentDir, config.getBackendId());
}
- /** {@inheritDoc} */
@Override
- public FilenameFilter getFilesToBackupFilter()
+ public ListIterator<Path> getFilesToBackup() throws DirectoryException
{
- return new FilenameFilter()
+ try
{
- @Override
- public boolean accept(File d, String name)
+ // FIXME: use full programmatic way of retrieving backup file once available in persistIt
+ String filesAsString = db.getManagement().execute("backup -f");
+ String[] allFiles = filesAsString.split("[\r\n]+");
+ final List<Path> files = new ArrayList<>();
+ for (String file : allFiles)
{
- return name.startsWith(VOLUME_NAME) && !name.endsWith(".lck");
+ files.add(Paths.get(file));
}
- };
+ return files.listIterator();
+ }
+ catch (RemoteException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKEND_LIST_FILES_TO_BACKUP.get(config.getBackendId(), stackTraceToSingleLineString(e)));
+ }
+ }
+
+ @Override
+ public Path beforeRestore() throws DirectoryException
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isDirectRestore()
+ {
+ // restore is done in an intermediate directory
+ return false;
+ }
+
+ @Override
+ public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
+ {
+ // intermediate directory content is moved to database directory
+ File targetDirectory = getDirectory();
+ recursiveDelete(targetDirectory);
+ try
+ {
+ Files.move(restoreDirectory, targetDirectory.toPath());
+ }
+ catch(IOException e)
+ {
+ LocalizableMessage msg = ERR_CANNOT_RENAME_RESTORE_DIRECTORY.get(restoreDirectory, targetDirectory.getPath());
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), msg);
+ }
+ }
+
+ /**
+ * Switch the database in append only mode.
+ * <p>
+ * This is a mandatory operation before performing a backup.
+ */
+ private void switchToAppendOnlyMode() throws DirectoryException
+ {
+ try
+ {
+ // FIXME: use full programmatic way of switching to this mode once available in persistIt
+ db.getManagement().execute("backup -y -a -c");
+ }
+ catch (RemoteException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKEND_SWITCH_TO_APPEND_MODE.get(config.getBackendId(), stackTraceToSingleLineString(e)));
+ }
+ }
+
+ /**
+ * Terminate the append only mode of the database.
+ * <p>
+ * This should be called only when database was previously switched to append only mode.
+ */
+ private void endAppendOnlyMode() throws DirectoryException
+ {
+ try
+ {
+ // FIXME: use full programmatic way of ending append mode once available in persistIt
+ db.getManagement().execute("backup -e");
+ }
+ catch (RemoteException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKEND_END_APPEND_MODE.get(config.getBackendId(), stackTraceToSingleLineString(e)));
+ }
+ }
+
+ @Override
+ public void createBackup(BackupConfig backupConfig) throws DirectoryException
+ {
+ switchToAppendOnlyMode();
+ try
+ {
+ new BackupManager(config.getBackendId()).createBackup(this, backupConfig);
+ }
+ finally
+ {
+ endAppendOnlyMode();
+ }
+ }
+
+ @Override
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
+ {
+ new BackupManager(config.getBackendId()).removeBackup(backupDirectory, backupID);
+ }
+
+ @Override
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
+ {
+ new BackupManager(config.getBackendId()).restoreBackup(this, restoreConfig);
}
/**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
index f2b0f6e..ba31796 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackendImpl.java
@@ -817,7 +817,7 @@
@Override
public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
- new BackupManager(getBackendID()).createBackup(storage, backupConfig);
+ storage.createBackup(backupConfig);
}
/** {@inheritDoc} */
@@ -825,14 +825,14 @@
public void removeBackup(BackupDirectory backupDirectory, String backupID)
throws DirectoryException
{
- new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
+ storage.removeBackup(backupDirectory, backupID);
}
/** {@inheritDoc} */
@Override
public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
- new BackupManager(getBackendID()).restoreBackup(storage, restoreConfig);
+ storage.restoreBackup(restoreConfig);
}
/**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackupManager.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackupManager.java
deleted file mode 100644
index 1c0abed..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/BackupManager.java
+++ /dev/null
@@ -1,1172 +0,0 @@
-/*
- * 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 legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * 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 legal-notices/CDDLv1_0.txt.
- * 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 2006-2009 Sun Microsystems, Inc.
- * Portions Copyright 2013-2015 ForgeRock AS.
- */
-package org.opends.server.backends.pluggable;
-
-import static org.opends.messages.BackendMessages.*;
-import static org.opends.messages.JebMessages.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.security.MessageDigest;
-import java.util.*;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Mac;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.opendj.config.server.ConfigException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.opends.server.backends.pluggable.spi.Storage;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.*;
-import org.opends.server.util.DynamicConstants;
-import org.opends.server.util.StaticUtils;
-
-/**
- * A backup manager for backends.
- */
-class BackupManager
-{
- private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
- /**
- * The common prefix for archive files.
- */
- private static final String BACKUP_BASE_FILENAME = "backup-";
-
- /**
- * The name of the property that holds the name of the latest log file
- * at the time the backup was created.
- */
- private static final String PROPERTY_LAST_LOGFILE_NAME = "last_logfile_name";
-
- /**
- * The name of the property that holds the size of the latest log file
- * at the time the backup was created.
- */
- private static final String PROPERTY_LAST_LOGFILE_SIZE = "last_logfile_size";
-
-
- /**
- * The name of the entry in an incremental backup archive file
- * containing a list of log files that are unchanged since the
- * previous backup.
- */
- private static final String ZIPENTRY_UNCHANGED_LOGFILES = "unchanged.txt";
-
- /**
- * The name of a dummy entry in the backup archive file that will act
- * as a placeholder in case a backup is done on an empty backend.
- */
- private static final String ZIPENTRY_EMPTY_PLACEHOLDER = "empty.placeholder";
-
-
- /**
- * The backend ID.
- */
- private final String backendID;
-
-
- /**
- * Construct a backup manager for a backend.
- * @param backendID The ID of the backend instance for which a backup
- * manager is required.
- */
- BackupManager(String backendID)
- {
- this.backendID = backendID;
- }
-
- /**
- * Create a backup of the backend. The backup is stored in a single zip
- * file in the backup directory. If the backup is incremental, then the
- * first entry in the zip is a text file containing a list of all the
- * log files that are unchanged since the previous backup. The remaining
- * zip entries are the log files themselves, which, for an incremental,
- * only include those files that have changed.
- * @param storage The underlying storage to be backed up.
- * @param backupConfig The configuration to use when performing the backup.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- void createBackup(Storage storage, BackupConfig backupConfig) throws DirectoryException
- {
- // Get the properties to use for the backup.
- String backupID = backupConfig.getBackupID();
- BackupDirectory backupDir = backupConfig.getBackupDirectory();
- boolean incremental = backupConfig.isIncremental();
- String incrBaseID = backupConfig.getIncrementalBaseID();
- boolean compress = backupConfig.compressData();
- boolean encrypt = backupConfig.encryptData();
- boolean hash = backupConfig.hashData();
- boolean signHash = backupConfig.signHash();
-
-
- HashMap<String,String> backupProperties = new HashMap<String,String>();
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
- String macKeyID = null;
-
- if (hash)
- {
- if (signHash)
- {
- try
- {
- macKeyID = cryptoManager.getMacEngineKeyEntryID();
- backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
-
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- else
- {
- String digestAlgorithm = cryptoManager
- .getPreferredMessageDigestAlgorithm();
- backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
-
- try
- {
- digest = cryptoManager.getPreferredMessageDigest();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- }
-
-
- // Date the backup.
- Date backupDate = new Date();
-
- // If this is an incremental, determine the base backup for this backup.
- HashSet<String> dependencies = new HashSet<String>();
- BackupInfo baseBackup = null;
-
- if (incremental)
- {
- if (incrBaseID == null && backupDir.getLatestBackup() != null)
- {
- // The default is to use the latest backup as base.
- incrBaseID = backupDir.getLatestBackup().getBackupID();
- }
-
- if (incrBaseID == null)
- {
- // No incremental backup ID: log a message informing that a backup
- // could not be found and that a normal backup will be done.
- incremental = false;
- logger.warn(WARN_BACKUPDB_INCREMENTAL_NOT_FOUND_DOING_NORMAL, backupDir.getPath());
- }
- else
- {
- baseBackup = getBackupInfo(backupDir, incrBaseID);
- }
- }
-
- // Get information about the latest log file from the base backup.
- String latestFileName = null;
- long latestFileSize = 0;
- if (baseBackup != null)
- {
- HashMap<String,String> properties = baseBackup.getBackupProperties();
- latestFileName = properties.get(PROPERTY_LAST_LOGFILE_NAME);
- latestFileSize = Long.parseLong(
- properties.get(PROPERTY_LAST_LOGFILE_SIZE));
- }
-
- /*
- Create an output stream that will be used to write the archive file. At
- its core, it will be a file output stream to put a file on the disk. If
- we are to encrypt the data, then that file output stream will be wrapped
- in a cipher output stream. The resulting output stream will then be
- wrapped by a zip output stream (which may or may not actually use
- compression).
- */
- String archiveFilename = null;
- OutputStream outputStream;
- File archiveFile;
- try
- {
- archiveFilename = BACKUP_BASE_FILENAME + backendID + "-" + backupID;
- archiveFile = new File(backupDir.getPath(), archiveFilename);
- if (archiveFile.exists())
- {
- int i=1;
- while (true)
- {
- archiveFile = new File(backupDir.getPath(),
- archiveFilename + "." + i);
- if (archiveFile.exists())
- {
- i++;
- }
- else
- {
- archiveFilename = archiveFilename + "." + i;
- break;
- }
- }
- }
-
- outputStream = new FileOutputStream(archiveFile, false);
- backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, archiveFilename);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
- get(archiveFilename, backupDir.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If we should encrypt the data, then wrap the output stream in a cipher
- // output stream.
- if (encrypt)
- {
- try
- {
- outputStream
- = cryptoManager.getCipherOutputStream(outputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file output stream in a zip output stream.
- ZipOutputStream zipStream = new ZipOutputStream(outputStream);
-
- LocalizableMessage message = ERR_JEB_BACKUP_ZIP_COMMENT.get(
- DynamicConstants.PRODUCT_NAME,
- backupID, backendID);
- zipStream.setComment(message.toString());
-
- if (compress)
- {
- zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
- }
- else
- {
- zipStream.setLevel(Deflater.NO_COMPRESSION);
- }
-
- File backendDir = storage.getDirectory();
- FilenameFilter filenameFilter = storage.getFilesToBackupFilter();
- File[] logFiles;
- try
- {
- logFiles = backendDir.listFiles(filenameFilter);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- if (logFiles == null)
- {
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
- }
-
- // Check to see if backend is empty. If so, insert placeholder entry into
- // archive
- if(logFiles.length <= 0)
- {
- try
- {
- ZipEntry emptyPlaceholder = new ZipEntry(ZIPENTRY_EMPTY_PLACEHOLDER);
- zipStream.putNextEntry(emptyPlaceholder);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- ZIPENTRY_EMPTY_PLACEHOLDER, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
-
- // Sort the log files from oldest to youngest since this is the order
- // in which they must be copied.
- // This is easy since the files are created in alphabetical order.
- Arrays.sort(logFiles);
-
- try
- {
- // Process log files that are unchanged from the base backup.
- int indexCurrent = 0;
- if (latestFileName != null)
- {
- ArrayList<String> unchangedList = new ArrayList<String>();
- while (indexCurrent < logFiles.length &&
- !backupConfig.isCancelled())
- {
- File logFile = logFiles[indexCurrent];
- String logFileName = logFile.getName();
-
- // Stop when we get to the first log file that has been
- // written since the base backup.
- int compareResult = logFileName.compareTo(latestFileName);
- if (compareResult > 0 ||
- (compareResult == 0 && logFile.length() != latestFileSize))
- {
- break;
- }
-
- logger.info(NOTE_JEB_BACKUP_FILE_UNCHANGED, logFileName);
-
- unchangedList.add(logFileName);
-
- indexCurrent++;
- }
-
- // Write a file containing the list of unchanged log files.
- if (!unchangedList.isEmpty())
- {
- String zipEntryName = ZIPENTRY_UNCHANGED_LOGFILES;
- try
- {
- archiveList(zipStream, mac, digest, zipEntryName, unchangedList);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- zipEntryName, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- // Set the dependency.
- dependencies.add(baseBackup.getBackupID());
- }
- }
-
- // Write the new log files to the zip file.
- do
- {
- boolean deletedFiles = false;
-
- while (indexCurrent < logFiles.length &&
- !backupConfig.isCancelled())
- {
- File logFile = logFiles[indexCurrent];
-
- try
- {
- latestFileSize = archiveFile(zipStream, mac, digest,
- logFile, backupConfig);
- latestFileName = logFile.getName();
- }
- catch (FileNotFoundException e)
- {
- logger.traceException(e);
-
- // A log file has been deleted by the cleaner since we started.
- deletedFiles = true;
- }
- catch (IOException e)
- {
- logger.traceException(e);
- message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
- logFile.getName(), stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- indexCurrent++;
- }
-
- if (deletedFiles)
- {
- /*
- The cleaner is active and has deleted one or more of the log files
- since we started. The in-use data from those log files will have
- been written to new log files, so we must include those new files.
- */
- final String latest = logFiles[logFiles.length-1].getName();
- FilenameFilter filter = new DBLatestFileFilter(latest, latestFileSize, filenameFilter);
-
- try
- {
- logFiles = backendDir.listFiles(filter);
- indexCurrent = 0;
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES.get(backendDir.getAbsolutePath(), archiveFilename);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
- if (logFiles == null)
- {
- break;
- }
-
- Arrays.sort(logFiles);
-
- logger.info(NOTE_JEB_BACKUP_CLEANER_ACTIVITY, logFiles.length);
- }
- else
- {
- // We are done.
- break;
- }
- }
- while (true);
-
- }
- // FIXME: The handling of exception below, plus the lack of finally block
- // to close the zipStream is clumsy. Needs cleanup and best practice.
- catch (DirectoryException e)
- {
- logger.traceException(e);
-
- try
- {
- zipStream.close();
- } catch (Exception e2) {}
- }
-
- // We're done writing the file, so close the zip stream (which should also
- // close the underlying stream).
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_CLOSE_ZIP_STREAM.
- get(archiveFilename, backupDir.getPath(),
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Get the digest or MAC bytes if appropriate.
- byte[] digestBytes = null;
- byte[] macBytes = null;
- if (hash)
- {
- if (signHash)
- {
- macBytes = mac.doFinal();
- }
- else
- {
- digestBytes = digest.digest();
- }
- }
-
-
- // Create a descriptor for this backup.
- backupProperties.put(PROPERTY_LAST_LOGFILE_NAME, latestFileName);
- backupProperties.put(PROPERTY_LAST_LOGFILE_SIZE,
- String.valueOf(latestFileSize));
- BackupInfo backupInfo = new BackupInfo(backupDir, backupID,
- backupDate, incremental, compress,
- encrypt, digestBytes, macBytes,
- dependencies, backupProperties);
-
- try
- {
- backupDir.addBackup(backupInfo);
- backupDir.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_JEB_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDir.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the backup if this operation was cancelled since the
- // backup may be incomplete
- if (backupConfig.isCancelled())
- {
- removeBackup(backupDir, backupID);
- }
- }
-
-
-
- /**
- * Restore a backend from backup, or verify the backup.
- * @param storage The underlying storage to be backed up.
- * @param restoreConfig The configuration to use when performing the restore.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- void restoreBackup(Storage storage, RestoreConfig restoreConfig) throws DirectoryException
- {
- // Get the properties to use for the restore.
- String backupID = restoreConfig.getBackupID();
- BackupDirectory backupDir = restoreConfig.getBackupDirectory();
- boolean verifyOnly = restoreConfig.verifyOnly();
-
- BackupInfo backupInfo = getBackupInfo(backupDir, backupID);
-
- // Create a restore directory with a different name to the backend
- // directory.
- File backendDir = storage.getDirectory();
- File restoreDir = new File(backendDir.getPath() + "-restore-" + backupID);
- if (!verifyOnly)
- {
- // FIXME: It's odd that we try to clean the directory before creating it
- cleanup(restoreDir);
- restoreDir.mkdir();
- }
-
- // Get the set of restore files that are in dependencies.
- Set<String> includeFiles;
- try
- {
- includeFiles = getUnchanged(backupDir, backupInfo);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_RESTORE.get(
- backupInfo.getBackupID(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Restore any dependencies.
- List<BackupInfo> dependents = getDependents(backupDir, backupInfo);
- for (BackupInfo dependent : dependents)
- {
- restoreArchive(restoreConfig, restoreDir, dependent, includeFiles);
- }
-
- // Restore the final archive file.
- restoreArchive(restoreConfig, restoreDir, backupInfo, null);
-
- // Delete the current backend directory and rename the restore directory.
- if (!verifyOnly)
- {
- StaticUtils.recursiveDelete(backendDir);
- if (!restoreDir.renameTo(backendDir))
- {
- LocalizableMessage msg = ERR_JEB_CANNOT_RENAME_RESTORE_DIRECTORY.get(
- restoreDir.getPath(), backendDir.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- msg);
- }
- }
- }
-
- private void cleanup(File directory) {
- File[] files = directory.listFiles();
- if (files != null)
- {
- for (File f : files)
- {
- f.delete();
- }
- }
- }
-
- /**
- * Removes the specified backup if it is possible to do so.
- *
- * @param backupDir The backup directory structure with which the
- * specified backup is associated.
- * @param backupID The backup ID for the backup to be removed.
- *
- * @throws DirectoryException If it is not possible to remove the specified
- * backup for some reason (e.g., no such backup
- * exists or there are other backups that are
- * dependent upon it).
- */
- void removeBackup(BackupDirectory backupDir,
- String backupID)
- throws DirectoryException
- {
- // Keep information about backup to be deleted for file removal
- BackupInfo backupInfo = getBackupInfo(backupDir, backupID);
-
- try
- {
- backupDir.removeBackup(backupID);
- }
- catch (ConfigException e)
- {
- logger.traceException(e);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- e.getMessageObject());
- }
-
- try
- {
- backupDir.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDir.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the archive file.
- File archiveFile = getArchiveFile(backupDir, backupInfo);
- archiveFile.delete();
-
- }
-
- private File getArchiveFile(BackupDirectory backupDir, BackupInfo backupInfo)
- {
- Map<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename = backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- return new File(backupDir.getPath(), archiveFilename);
- }
-
- private void restoreArchive(RestoreConfig restoreConfig, File restoreDir, BackupInfo dependent,
- Set<String> includeFiles) throws DirectoryException
- {
- try
- {
- restoreArchive(restoreDir, restoreConfig, dependent, includeFiles);
- }
- catch (IOException e)
- {
- logger.traceException(e);
- LocalizableMessage message =
- ERR_JEB_BACKUP_CANNOT_RESTORE.get(dependent.getBackupID(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
-
- /**
- * Restore the contents of an archive file. If the archive is being
- * restored as a dependency, then only files in the specified set
- * are restored, and the restored files are removed from the set. Otherwise
- * all files from the archive are restored, and files that are to be found
- * in dependencies are added to the set.
- *
- * @param restoreDir The directory in which files are to be restored.
- * @param restoreConfig The restore configuration.
- * @param backupInfo The backup containing the files to be restored.
- * @param includeFiles The set of files to be restored. If null, then
- * all files are restored.
- * @throws DirectoryException If a Directory Server error occurs.
- * @throws IOException If an I/O exception occurs during the restore.
- */
- private void restoreArchive(File restoreDir,
- RestoreConfig restoreConfig,
- BackupInfo backupInfo,
- Set<String> includeFiles)
- throws DirectoryException,IOException
- {
- BackupDirectory backupDir = restoreConfig.getBackupDirectory();
- boolean verifyOnly = restoreConfig.verifyOnly();
-
- String backupID = backupInfo.getBackupID();
- byte[] hash = backupInfo.getUnsignedHash();
- byte[] signHash = backupInfo.getSignedHash();
-
- HashMap<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename =
- backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = getMacEngine(signHash, backupProperties, cryptoManager);
- MessageDigest digest = getMessageDigest(hash, backupProperties, cryptoManager);
-
- File archiveFile = new File(backupDir.getPath(), archiveFilename);
- ZipInputStream zipStream = getZipInputStream(archiveFile, backupInfo, cryptoManager);
-
- // Iterate through the entries in the zip file.
- ZipEntry zipEntry = zipStream.getNextEntry();
- while (zipEntry != null && !restoreConfig.isCancelled())
- {
- String name = zipEntry.getName();
-
- if (ZIPENTRY_EMPTY_PLACEHOLDER.equals(name))
- {
- // This entry is treated specially to indicate a backup of an empty
- // backend was attempted.
-
- zipEntry = zipStream.getNextEntry();
- continue;
- }
-
- if (ZIPENTRY_UNCHANGED_LOGFILES.equals(name))
- {
- // This entry is treated specially. It is never restored,
- // and its hash is computed on the strings, not the bytes.
- if (mac != null || digest != null)
- {
- // The file name is part of the hash.
- update(mac, digest, name);
-
- ArrayList<String> lines = readAllLines(zipStream);
- for (String line : lines)
- {
- update(mac, digest, line);
- }
- }
-
- zipEntry = zipStream.getNextEntry();
- continue;
- }
-
- // See if we need to restore the file.
- File file = new File(restoreDir, name);
- OutputStream outputStream = null;
- if ((includeFiles == null || includeFiles.contains(zipEntry.getName())) && !verifyOnly)
- {
- outputStream = new FileOutputStream(file);
- }
-
- if (outputStream != null || mac != null || digest != null)
- {
- if (verifyOnly)
- {
- logger.info(NOTE_JEB_BACKUP_VERIFY_FILE, zipEntry.getName());
- }
-
- // The file name is part of the hash.
- update(mac, digest, name);
-
- // Process the file.
- long totalBytesRead = 0;
- byte[] buffer = new byte[8192];
- int bytesRead = zipStream.read(buffer);
- while (bytesRead > 0 && !restoreConfig.isCancelled())
- {
- totalBytesRead += bytesRead;
-
- update(mac, digest, buffer, 0, bytesRead);
-
- if (outputStream != null)
- {
- outputStream.write(buffer, 0, bytesRead);
- }
-
- bytesRead = zipStream.read(buffer);
- }
-
- if (outputStream != null)
- {
- outputStream.close();
-
- logger.info(NOTE_JEB_BACKUP_RESTORED_FILE, zipEntry.getName(), totalBytesRead);
- }
- }
-
- zipEntry = zipStream.getNextEntry();
- }
-
- zipStream.close();
-
- // Check the hash.
- if (digest != null && !Arrays.equals(digest.digest(), hash))
- {
- LocalizableMessage message = ERR_JEB_BACKUP_UNSIGNED_HASH_ERROR.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
- }
-
- if (mac != null && !Arrays.equals(mac.doFinal(), signHash))
- {
- LocalizableMessage message = ERR_JEB_BACKUP_SIGNED_HASH_ERROR.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
- }
- }
-
- private Mac getMacEngine(byte[] signHash, HashMap<String, String> backupProperties, CryptoManager cryptoManager)
- throws DirectoryException
- {
- if (signHash != null)
- {
- String macKeyID = backupProperties.get(BACKUP_PROPERTY_MAC_KEY_ID);
- try
- {
- return cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_MAC.get(macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- return null;
- }
-
- private MessageDigest getMessageDigest(byte[] hash, HashMap<String, String> backupProperties,
- CryptoManager cryptoManager) throws DirectoryException
- {
- if (hash != null)
- {
- String digestAlgorithm = backupProperties.get(BACKUP_PROPERTY_DIGEST_ALGORITHM);
- try
- {
- return cryptoManager.getMessageDigest(digestAlgorithm);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message =
- ERR_JEB_BACKUP_CANNOT_GET_DIGEST.get(digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
- return null;
- }
-
- private ZipInputStream getZipInputStream(File archiveFile, BackupInfo backupInfo, CryptoManager cryptoManager)
- throws FileNotFoundException, DirectoryException
- {
- InputStream inputStream = new FileInputStream(archiveFile);
-
- // If the data is encrypted, then wrap the input stream in a cipher input stream
- if (backupInfo.isEncrypted())
- {
- try
- {
- inputStream = cryptoManager.getCipherInputStream(inputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_JEB_BACKUP_CANNOT_GET_CIPHER.get(stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
- }
-
- // Wrap the file input stream in a zip input stream.
- return new ZipInputStream(inputStream);
- }
-
- /**
- * Writes a file to an entry in the archive file.
- * @param zipStream The zip output stream to which the file is to be
- * written.
- * @param mac A message authentication code to be updated, if not null.
- * @param digest A message digest to be updated, if not null.
- * @param file The file to be written.
- * @return The number of bytes written from the file.
- * @throws FileNotFoundException If the file to be archived does not exist.
- * @throws IOException If an I/O error occurs while archiving the file.
- */
- private long archiveFile(ZipOutputStream zipStream,
- Mac mac, MessageDigest digest, File file,
- BackupConfig backupConfig)
- throws IOException, FileNotFoundException
- {
- ZipEntry zipEntry = new ZipEntry(file.getName());
-
- // Open the file for reading.
- InputStream inputStream = new FileInputStream(file);
-
- // Start the zip entry.
- zipStream.putNextEntry(zipEntry);
-
- // Put the name in the hash.
- update(mac, digest, file.getName());
-
- // Write the file.
- long totalBytesRead = 0;
- byte[] buffer = new byte[8192];
- int bytesRead = inputStream.read(buffer);
- while (bytesRead > 0 && !backupConfig.isCancelled())
- {
- update(mac, digest, buffer, 0, bytesRead);
-
- zipStream.write(buffer, 0, bytesRead);
- totalBytesRead += bytesRead;
- bytesRead = inputStream.read(buffer);
- }
- inputStream.close();
-
- // Finish the zip entry.
- zipStream.closeEntry();
-
- logger.info(NOTE_JEB_BACKUP_ARCHIVED_FILE, zipEntry.getName());
-
- return totalBytesRead;
- }
-
- private void update(Mac mac, MessageDigest digest, byte[] buffer, int offset, int len)
- {
- if (mac != null)
- {
- mac.update(buffer, offset, len);
- }
- if (digest != null)
- {
- digest.update(buffer, offset, len);
- }
- }
-
- private void update(Mac mac, MessageDigest digest, String s)
- {
- if (mac != null || digest != null)
- {
- final byte[] bytes = getBytes(s);
-
- if (mac != null)
- {
- mac.update(bytes);
- }
- if (digest != null)
- {
- digest.update(bytes);
- }
- }
- }
-
- /**
- * Write a list of strings to an entry in the archive file.
- * @param zipStream The zip output stream to which the entry is to be
- * written.
- * @param mac An optional MAC to be updated.
- * @param digest An optional message digest to be updated.
- * @param fileName The name of the zip entry to be written.
- * @param list A list of strings to be written. The strings must not
- * contain newlines.
- * @throws IOException If an I/O error occurs while writing the archive entry.
- */
- private void archiveList(ZipOutputStream zipStream,
- Mac mac, MessageDigest digest, String fileName,
- List<String> list)
- throws IOException
- {
- ZipEntry zipEntry = new ZipEntry(fileName);
-
- // Start the zip entry.
- zipStream.putNextEntry(zipEntry);
-
- // Put the name in the hash.
- update(mac, digest, fileName);
-
- Writer writer = new OutputStreamWriter(zipStream);
- for (String s : list)
- {
- update(mac, digest, s);
-
- writer.write(s);
- writer.write(EOL);
- }
- writer.flush();
-
- // Finish the zip entry.
- zipStream.closeEntry();
- }
-
- /**
- * Obtains the set of files in a backup that are unchanged from its
- * dependent backup or backups. This list is stored as the first entry
- * in the archive file.
- * @param backupDir The backup directory.
- * @param backupInfo The backup info.
- * @return The set of files that were unchanged.
- * @throws DirectoryException If an error occurs while trying to get the
- * appropriate cipher algorithm for an encrypted backup.
- * @throws IOException If an I/O error occurs while reading the backup
- * archive file.
- */
- private Set<String> getUnchanged(BackupDirectory backupDir,
- BackupInfo backupInfo)
- throws DirectoryException, IOException
- {
- HashSet<String> hashSet = new HashSet<String>();
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
-
- File archiveFile = getArchiveFile(backupDir, backupInfo);
- ZipInputStream zipStream = getZipInputStream(archiveFile, backupInfo, cryptoManager);
-
- // Iterate through the entries in the zip file.
- ZipEntry zipEntry = zipStream.getNextEntry();
- while (zipEntry != null)
- {
- // We are looking for the entry containing the list of unchanged files.
- if (ZIPENTRY_UNCHANGED_LOGFILES.equals(zipEntry.getName()))
- {
- hashSet.addAll(readAllLines(zipStream));
- break;
- }
-
- zipEntry = zipStream.getNextEntry();
- }
-
- zipStream.close();
- return hashSet;
- }
-
- private ArrayList<String> readAllLines(ZipInputStream zipStream) throws IOException
- {
- final ArrayList<String> results = new ArrayList<String>();
-
- String line;
- BufferedReader reader = new BufferedReader(new InputStreamReader(zipStream));
- while ((line = reader.readLine()) != null)
- {
- results.add(line);
- }
- return results;
- }
-
- /**
- * Obtains a list of the dependencies of a given backup in order from
- * the oldest (the full backup), to the most recent.
- * @param backupDir The backup directory.
- * @param backupInfo The backup for which dependencies are required.
- * @return A list of dependent backups.
- * @throws DirectoryException If a Directory Server error occurs.
- */
- private ArrayList<BackupInfo> getDependents(BackupDirectory backupDir,
- BackupInfo backupInfo)
- throws DirectoryException
- {
- ArrayList<BackupInfo> dependents = new ArrayList<BackupInfo>();
- while (backupInfo != null && !backupInfo.getDependencies().isEmpty())
- {
- String backupID = backupInfo.getDependencies().iterator().next();
- backupInfo = getBackupInfo(backupDir, backupID);
- if (backupInfo != null)
- {
- dependents.add(backupInfo);
- }
- }
- Collections.reverse(dependents);
- return dependents;
- }
-
- /**
- * Get the information for a given backup ID from the backup directory.
- * @param backupDir The backup directory.
- * @param backupID The backup ID.
- * @return The backup information, never null.
- * @throws DirectoryException If the backup information cannot be found.
- */
- private BackupInfo getBackupInfo(BackupDirectory backupDir,
- String backupID) throws DirectoryException
- {
- BackupInfo backupInfo = backupDir.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message = ERR_BACKUP_MISSING_BACKUPID.get(backupID, backupDir.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
- }
- return backupInfo;
- }
-
- /**
- * This class implements a FilenameFilter to detect the last file from a database.
- */
- private static class DBLatestFileFilter implements FilenameFilter {
- private final String latest;
- private final long latestSize;
- private FilenameFilter filenameFilter;
-
- public DBLatestFileFilter(String latest, long latestSize, FilenameFilter filenameFilter) {
- this.latest = latest;
- this.latestSize = latestSize;
- this.filenameFilter = filenameFilter;
- }
-
- @Override
- public boolean accept(File d, String name)
- {
- if (!filenameFilter.accept(d, name))
- {
- return false;
- }
- int compareTo = name.compareTo(latest);
- return compareTo > 0 || (compareTo == 0 && d.length() > latestSize);
- }
- }
-}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/TracedStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/TracedStorage.java
index e3a4a12..6e759e6 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/TracedStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/TracedStorage.java
@@ -25,9 +25,6 @@
*/
package org.opends.server.backends.pluggable;
-import java.io.File;
-import java.io.FilenameFilter;
-
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
@@ -42,6 +39,10 @@
import org.opends.server.backends.pluggable.spi.UpdateFunction;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.RestoreConfig;
/**
* Decorates a {@link Storage} with additional trace logging.
@@ -249,18 +250,6 @@
}
@Override
- public File getDirectory()
- {
- return storage.getDirectory();
- }
-
- @Override
- public FilenameFilter getFilesToBackupFilter()
- {
- return storage.getFilesToBackupFilter();
- }
-
- @Override
public StorageStatus getStorageStatus()
{
return storage.getStorageStatus();
@@ -340,6 +329,36 @@
storage.write(op);
}
+ @Override
+ public void createBackup(BackupConfig backupConfig) throws DirectoryException
+ {
+ storage.createBackup(backupConfig);
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("Storage@%s.createBackup(%s)", storageId(), backendId);
+ }
+ }
+
+ @Override
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
+ {
+ storage.removeBackup(backupDirectory, backupID);
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("Storage@%s.removeBackup(%s, %s)", storageId(), backupID, backendId);
+ }
+ }
+
+ @Override
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
+ {
+ storage.restoreBackup(restoreConfig);
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("Storage@%s.restoreBackup(%s)", storageId(), backendId);
+ }
+ }
+
private String hex(final ByteSequence bytes)
{
return bytes != null ? bytes.toByteString().toHexString() : null;
@@ -349,4 +368,6 @@
{
return System.identityHashCode(this);
}
+
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
index ec43495..3bf0247 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/Storage.java
@@ -26,8 +26,11 @@
package org.opends.server.backends.pluggable.spi;
import java.io.Closeable;
-import java.io.File;
-import java.io.FilenameFilter;
+
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.RestoreConfig;
/**
* This interface abstracts the underlying storage engine,
@@ -100,27 +103,40 @@
*/
boolean supportsBackupAndRestore();
- /**
- * Returns the file system directory in which any database files are located. This method is
- * called when performing backup and restore operations.
- *
- * @return The file system directory in which any database files are located.
- * @throws UnsupportedOperationException
- * If backup and restore is not supported by this storage.
- */
- File getDirectory();
-
- /**
- * Returns a filename filter which selects the files to be included in a backup. This method is
- * called when performing backup operations.
- *
- * @return A filename filter which selects the files to be included in a backup.
- * @throws UnsupportedOperationException
- * If backup and restore is not supported by this storage.
- */
- FilenameFilter getFilesToBackupFilter();
-
/** {@inheritDoc} */
@Override
void close();
+
+ /**
+ * Creates a backup for this storage.
+ *
+ * @param backupConfig
+ * The configuration to use when performing the backup.
+ * @throws DirectoryException
+ * If a Directory Server error occurs.
+ */
+ void createBackup(BackupConfig backupConfig) throws DirectoryException;
+
+ /**
+ * Removes a backup for this storage.
+ *
+ * @param backupDirectory
+ * The backup directory structure with which the specified backup is
+ * associated.
+ * @param backupID
+ * The backup ID for the backup to be removed.
+ * @throws DirectoryException
+ * If it is not possible to remove the specified backup.
+ */
+ void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException;
+
+ /**
+ * Restores a backup for this storage.
+ *
+ * @param restoreConfig
+ * The configuration to use when performing the restore.
+ * @throws DirectoryException
+ * If a Directory Server error occurs.
+ */
+ void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException;
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
index 91ed7c8..519fe71 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
@@ -29,19 +29,18 @@
import static org.forgerock.util.Reject.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.server.config.ConfigConstants.*;
-import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
-import java.io.*;
+import java.io.File;
+import java.io.FileFilter;
import java.net.InetAddress;
-import java.security.MessageDigest;
-import java.util.*;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Mac;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -56,14 +55,16 @@
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.TaskBackendCfg;
import org.opends.server.api.Backend;
+import org.opends.server.api.Backupable;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.types.LockManager.DNLock;
-import org.opends.server.util.DynamicConstants;
+import org.opends.server.util.BackupManager;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
+import org.opends.server.util.StaticUtils;
/**
* This class provides an implementation of a Directory Server backend that may
@@ -72,8 +73,9 @@
*/
public class TaskBackend
extends Backend<TaskBackendCfg>
- implements ConfigurationChangeListener<TaskBackendCfg>
+ implements ConfigurationChangeListener<TaskBackendCfg>, Backupable
{
+
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -1094,633 +1096,24 @@
/** {@inheritDoc} */
@Override
- public void createBackup(BackupConfig backupConfig)
- throws DirectoryException
+ public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
- // Get the properties to use for the backup. We don't care whether or not
- // it's incremental, so there's no need to get that.
- String backupID = backupConfig.getBackupID();
- BackupDirectory backupDirectory = backupConfig.getBackupDirectory();
- boolean compress = backupConfig.compressData();
- boolean encrypt = backupConfig.encryptData();
- boolean hash = backupConfig.hashData();
- boolean signHash = backupConfig.signHash();
-
-
- // Create a hash map that will hold the extra backup property information
- // for this backup.
- HashMap<String,String> backupProperties = new HashMap<String,String>();
-
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
- String digestAlgorithm = null;
- String macKeyID = null;
-
- if (hash)
- {
- if (signHash)
- {
- try
- {
- macKeyID = cryptoManager.getMacEngineKeyEntryID();
- backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
-
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_TASKS_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- else
- {
- digestAlgorithm = cryptoManager.getPreferredMessageDigestAlgorithm();
- backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
-
- try
- {
- digest = cryptoManager.getPreferredMessageDigest();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_TASKS_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- }
-
-
- // Create an output stream that will be used to write the archive file. At
- // its core, it will be a file output stream to put a file on the disk. If
- // we are to encrypt the data, then that file output stream will be wrapped
- // in a cipher output stream. The resulting output stream will then be
- // wrapped by a zip output stream (which may or may not actually use
- // compression).
- String filename = null;
- OutputStream outputStream;
- try
- {
- filename = TASKS_BACKUP_BASE_FILENAME + backupID;
- File archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename);
- if (archiveFile.exists())
- {
- int i=1;
- while (true)
- {
- archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename + "." + i);
- if (archiveFile.exists())
- {
- i++;
- }
- else
- {
- filename = filename + "." + i;
- break;
- }
- }
- }
-
- outputStream = new FileOutputStream(archiveFile, false);
- backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, filename);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_TASKS_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
- get(filename, backupDirectory.getPath(), getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
-
- // If we should encrypt the data, then wrap the output stream in a cipher
- // output stream.
- if (encrypt)
- {
- try
- {
- outputStream
- = cryptoManager.getCipherOutputStream(outputStream);
- }
- catch (CryptoManagerException e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_TASKS_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file output stream in a zip output stream.
- ZipOutputStream zipStream = new ZipOutputStream(outputStream);
-
- LocalizableMessage message = ERR_TASKS_BACKUP_ZIP_COMMENT.get(
- DynamicConstants.PRODUCT_NAME,
- backupID);
- zipStream.setComment(String.valueOf(message));
-
- if (compress)
- {
- zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
- }
- else
- {
- zipStream.setLevel(Deflater.NO_COMPRESSION);
- }
-
- // Take tasks file and write it to the zip stream. If we
- // are using a hash or MAC, then calculate that as well.
- byte[] buffer = new byte[8192];
- File tasksFile = getFileForPath(taskBackingFile);
- String baseName = tasksFile.getName();
-
- // We'll put the name in the hash, too.
- if (hash) {
- if (signHash) {
- mac.update(getBytes(baseName));
- } else {
- digest.update(getBytes(baseName));
- }
- }
-
- InputStream inputStream = null;
- try {
- ZipEntry zipEntry = new ZipEntry(baseName);
- zipStream.putNextEntry(zipEntry);
-
- inputStream = new FileInputStream(tasksFile);
- while (true) {
- int bytesRead = inputStream.read(buffer);
- if (bytesRead < 0 || backupConfig.isCancelled()) {
- break;
- }
-
- if (hash) {
- if (signHash) {
- mac.update(buffer, 0, bytesRead);
- } else {
- digest.update(buffer, 0, bytesRead);
- }
- }
-
- zipStream.write(buffer, 0, bytesRead);
- }
-
- zipStream.closeEntry();
- inputStream.close();
- } catch (Exception e) {
- logger.traceException(e);
- close(inputStream, zipStream);
-
- message = ERR_TASKS_BACKUP_CANNOT_BACKUP_TASKS_FILE.get(baseName,
- stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // We're done writing the file, so close the zip stream (which should also
- // close the underlying stream).
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_TASKS_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get(
- filename, backupDirectory.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Get the digest or MAC bytes if appropriate.
- byte[] digestBytes = null;
- byte[] macBytes = null;
- if (hash)
- {
- if (signHash)
- {
- macBytes = mac.doFinal();
- }
- else
- {
- digestBytes = digest.digest();
- }
- }
-
-
- // Create the backup info structure for this backup and add it to the backup
- // directory.
- // FIXME -- Should I use the date from when I started or finished?
- BackupInfo backupInfo = new BackupInfo(backupDirectory, backupID,
- new Date(), false, compress,
- encrypt, digestBytes, macBytes,
- null, backupProperties);
-
- try
- {
- backupDirectory.addBackup(backupInfo);
- backupDirectory.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_TASKS_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
-
- /** {@inheritDoc} */
- @Override
- public void removeBackup(BackupDirectory backupDirectory,
- String backupID)
- throws DirectoryException
- {
- BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message = ERR_BACKUP_MISSING_BACKUPID.get(backupID,
- backupDirectory.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- HashMap<String,String> backupProperties = backupInfo.getBackupProperties();
-
- String archiveFilename =
- backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- File archiveFile = new File(backupDirectory.getPath(), archiveFilename);
-
- try
- {
- backupDirectory.removeBackup(backupID);
- }
- catch (ConfigException e)
- {
- logger.traceException(e);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- e.getMessageObject());
- }
-
- try
- {
- backupDirectory.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the archive file.
- archiveFile.delete();
+ new BackupManager(getBackendID()).createBackup(this, backupConfig);
}
/** {@inheritDoc} */
@Override
- public void restoreBackup(RestoreConfig restoreConfig)
- throws DirectoryException
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
- // First, make sure that the requested backup exists.
- BackupDirectory backupDirectory = restoreConfig.getBackupDirectory();
- String backupPath = backupDirectory.getPath();
- String backupID = restoreConfig.getBackupID();
- BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
- boolean verifyOnly = restoreConfig.verifyOnly();
-
- if (backupInfo == null)
- {
- LocalizableMessage message =
- ERR_TASKS_RESTORE_NO_SUCH_BACKUP.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- // Read the backup info structure to determine the name of the file that
- // contains the archive. Then make sure that file exists.
- String backupFilename =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- if (backupFilename == null)
- {
- LocalizableMessage message =
- ERR_TASKS_RESTORE_NO_BACKUP_FILE.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- File backupFile = new File(backupPath + File.separator + backupFilename);
- try
- {
- if (! backupFile.exists())
- {
- LocalizableMessage message =
- ERR_TASKS_RESTORE_NO_SUCH_FILE.get(backupID, backupFile.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
- catch (DirectoryException de)
- {
- throw de;
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_CHECK_FOR_ARCHIVE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If the backup is hashed, then we need to get the message digest to use
- // to verify it.
- byte[] unsignedHash = backupInfo.getUnsignedHash();
- MessageDigest digest = null;
- if (unsignedHash != null)
- {
- String digestAlgorithm =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM);
- if (digestAlgorithm == null)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_UNKNOWN_DIGEST.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- digest = DirectoryServer.getCryptoManager().getMessageDigest(
- digestAlgorithm);
- }
- catch (Exception e)
- {
- LocalizableMessage message =
- ERR_TASKS_RESTORE_CANNOT_GET_DIGEST.get(backupID, digestAlgorithm);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // If the backup is signed, then we need to get the MAC to use to verify it.
- byte[] signedHash = backupInfo.getSignedHash();
- Mac mac = null;
- if (signedHash != null)
- {
- String macKeyID =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
- if (macKeyID == null)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_UNKNOWN_MAC.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_GET_MAC.get(
- backupID, macKeyID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Create the input stream that will be used to read the backup file. At
- // its core, it will be a file input stream.
- InputStream inputStream;
- try
- {
- inputStream = new FileInputStream(backupFile);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_OPEN_BACKUP_FILE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If the backup is encrypted, then we need to wrap the file input stream
- // in a cipher input stream.
- if (backupInfo.isEncrypted())
- {
- try
- {
- inputStream = DirectoryServer.getCryptoManager()
- .getCipherInputStream(inputStream);
- }
- catch (CryptoManagerException e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_GET_CIPHER.get(
- backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Now wrap the resulting input stream in a zip stream so that we can read
- // its contents. We don't need to worry about whether to use compression or
- // not because it will be handled automatically.
- ZipInputStream zipStream = new ZipInputStream(inputStream);
-
- // Read through the archive file an entry at a time. For each entry, update
- // the digest or MAC if necessary.
- byte[] buffer = new byte[8192];
- while (true)
- {
- ZipEntry zipEntry;
- try
- {
- zipEntry = zipStream.getNextEntry();
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_GET_ZIP_ENTRY.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- if (zipEntry == null)
- {
- break;
- }
-
- // Get the filename for the zip entry and update the digest or MAC as
- // necessary.
- String fileName = zipEntry.getName();
- if (digest != null)
- {
- digest.update(getBytes(fileName));
- }
- if (mac != null)
- {
- mac.update(getBytes(fileName));
- }
-
- // If we're doing the restore, then create the output stream to write the
- // file.
- File tasksFile = getFileForPath(taskBackingFile);
- String baseDirPath = tasksFile.getParent();
- OutputStream outputStream = null;
-
- if (!verifyOnly)
- {
- String filePath = baseDirPath + File.separator + fileName;
- try
- {
- outputStream = new FileOutputStream(filePath);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_CREATE_FILE.get(
- backupID, filePath, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
-
- // Read the contents of the file and update the digest or MAC as
- // necessary.
- try
- {
- while (true)
- {
- int bytesRead = zipStream.read(buffer);
- if (bytesRead < 0)
- {
- // We've reached the end of the entry.
- break;
- }
-
- // Update the digest or MAC if appropriate.
- if (digest != null)
- {
- digest.update(buffer, 0, bytesRead);
- }
-
- if (mac != null)
- {
- mac.update(buffer, 0, bytesRead);
- }
-
- // Write the data to the output stream if appropriate.
- if (outputStream != null)
- {
- outputStream.write(buffer, 0, bytesRead);
- }
- }
-
- // We're at the end of the file so close the output stream if we're
- // writing it.
- if (outputStream != null)
- {
- outputStream.close();
- }
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_CANNOT_PROCESS_ARCHIVE_FILE.get(
- backupID, fileName, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Close the zip stream since we don't need it anymore.
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_ERROR_ON_ZIP_STREAM_CLOSE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // At this point, we should be done with the contents of the ZIP file and
- // the restore should be complete. If we were generating a digest or MAC,
- // then make sure it checks out.
- if (digest != null)
- {
- byte[] calculatedHash = digest.digest();
- if (Arrays.equals(calculatedHash, unsignedHash))
- {
- logger.info(NOTE_TASKS_RESTORE_UNSIGNED_HASH_VALID);
- }
- else
- {
- LocalizableMessage message =
- ERR_TASKS_RESTORE_UNSIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
- if (mac != null)
- {
- byte[] calculatedSignature = mac.doFinal();
- if (Arrays.equals(calculatedSignature, signedHash))
- {
- logger.info(NOTE_TASKS_RESTORE_SIGNED_HASH_VALID);
- }
- else
- {
- LocalizableMessage message = ERR_TASKS_RESTORE_SIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
- // If we are just verifying the archive, then we're done.
- if (verifyOnly)
- {
- logger.info(NOTE_TASKS_RESTORE_VERIFY_SUCCESSFUL, backupID, backupPath);
- return;
- }
-
- // If we've gotten here, then the archive was restored successfully.
- logger.info(NOTE_TASKS_RESTORE_SUCCESSFUL, backupID, backupPath);
+ new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
}
-
+ /** {@inheritDoc} */
+ @Override
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
+ {
+ new BackupManager(getBackendID()).restoreBackup(this, restoreConfig);
+ }
/** {@inheritDoc} */
@Override
@@ -2052,8 +1445,59 @@
/** {@inheritDoc} */
@Override
- public void preloadEntryCache() throws UnsupportedOperationException {
+ public void preloadEntryCache() throws UnsupportedOperationException
+ {
throw new UnsupportedOperationException("Operation not supported.");
}
+
+ /** {@inheritDoc} */
+ @Override
+ public File getDirectory()
+ {
+ return getFileForPath(taskBackingFile).getParentFile();
+ }
+
+ private FileFilter getFilesToBackupFilter()
+ {
+ return new FileFilter()
+ {
+ @Override
+ public boolean accept(File file)
+ {
+ return file.getName().equals(getFileForPath(taskBackingFile).getName());
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<Path> getFilesToBackup() throws DirectoryException
+ {
+ return BackupManager.getFiles(getDirectory(), getFilesToBackupFilter(), getBackendID()).listIterator();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isDirectRestore()
+ {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path beforeRestore() throws DirectoryException
+ {
+ // save current files
+ return BackupManager.saveCurrentFilesToDirectory(this, getBackendID());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
+ {
+ // restore was successful, delete the save directory
+ StaticUtils.recursiveDelete(saveDirectory.toFile());
+ }
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
index 3d8bf6d..e89e878 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/ConfigFileHandler.java
@@ -38,19 +38,13 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
+import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Mac;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -65,6 +59,7 @@
import org.forgerock.util.Utils;
import org.opends.server.admin.std.server.ConfigFileHandlerBackendCfg;
import org.opends.server.api.AlertGenerator;
+import org.opends.server.api.Backupable;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConfigAddListener;
import org.opends.server.api.ConfigChangeListener;
@@ -81,7 +76,7 @@
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.tools.LDIFModify;
import org.opends.server.types.*;
-import org.opends.server.util.DynamicConstants;
+import org.opends.server.util.BackupManager;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
@@ -94,7 +89,7 @@
*/
public class ConfigFileHandler
extends ConfigHandler<ConfigFileHandlerBackendCfg>
- implements AlertGenerator
+ implements AlertGenerator, Backupable
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -1961,819 +1956,23 @@
/** {@inheritDoc} */
@Override
- public void createBackup(BackupConfig backupConfig)
- throws DirectoryException
+ public void createBackup(BackupConfig backupConfig) throws DirectoryException
{
- // Get the properties to use for the backup. We don't care whether or not
- // it's incremental, so there's no need to get that.
- String backupID = backupConfig.getBackupID();
- BackupDirectory backupDirectory = backupConfig.getBackupDirectory();
- boolean compress = backupConfig.compressData();
- boolean encrypt = backupConfig.encryptData();
- boolean hash = backupConfig.hashData();
- boolean signHash = backupConfig.signHash();
-
-
- // Create a hash map that will hold the extra backup property information
- // for this backup.
- HashMap<String,String> backupProperties = new HashMap<String,String>();
-
-
- // Get the crypto manager and use it to obtain references to the message
- // digest and/or MAC to use for hashing and/or signing.
- CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
- Mac mac = null;
- MessageDigest digest = null;
- String macKeyID = null;
-
- if (hash)
- {
- if (signHash)
- {
- try
- {
- macKeyID = cryptoManager.getMacEngineKeyEntryID();
- backupProperties.put(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
-
- mac = cryptoManager.getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_CONFIG_BACKUP_CANNOT_GET_MAC.get(
- macKeyID, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- else
- {
- String digestAlgorithm =
- cryptoManager.getPreferredMessageDigestAlgorithm();
- backupProperties.put(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
-
- try
- {
- digest = cryptoManager.getPreferredMessageDigest();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_CONFIG_BACKUP_CANNOT_GET_DIGEST.get(
- digestAlgorithm, stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
- }
-
-
- // Create an output stream that will be used to write the archive file. At
- // its core, it will be a file output stream to put a file on the disk. If
- // we are to encrypt the data, then that file output stream will be wrapped
- // in a cipher output stream. The resulting output stream will then be
- // wrapped by a zip output stream (which may or may not actually use
- // compression).
- String filename = null;
- OutputStream outputStream;
- try
- {
- filename = CONFIG_BACKUP_BASE_FILENAME + backupID;
- File archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename);
- if (archiveFile.exists())
- {
- int i=1;
- while (true)
- {
- archiveFile = new File(backupDirectory.getPath() + File.separator +
- filename + "." + i);
- if (archiveFile.exists())
- {
- i++;
- }
- else
- {
- filename = filename + "." + i;
- break;
- }
- }
- }
-
- outputStream = new FileOutputStream(archiveFile, false);
- backupProperties.put(BACKUP_PROPERTY_ARCHIVE_FILENAME, filename);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_CONFIG_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
- get(filename, backupDirectory.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
-
- // If we should encrypt the data, then wrap the output stream in a cipher
- // output stream.
- if (encrypt)
- {
- try
- {
- outputStream
- = cryptoManager.getCipherOutputStream(outputStream);
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- LocalizableMessage message = ERR_CONFIG_BACKUP_CANNOT_GET_CIPHER.get(
- stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Wrap the file output stream in a zip output stream.
- ZipOutputStream zipStream = new ZipOutputStream(outputStream);
-
- LocalizableMessage message = ERR_CONFIG_BACKUP_ZIP_COMMENT.get(
- DynamicConstants.PRODUCT_NAME,
- backupID);
- zipStream.setComment(message.toString());
-
- if (compress)
- {
- zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
- }
- else
- {
- zipStream.setLevel(Deflater.NO_COMPRESSION);
- }
-
-
- // This may seem a little weird, but in this context, we only have access to
- // this class as a backend and not as the config handler. We need it as a
- // config handler to determine the path to the config file, so we can get
- // that from the Directory Server object.
- String configFile = null;
- try
- {
- configFile =
- ((ConfigFileHandler) DirectoryServer.getConfigHandler()).configFile;
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_CONFIG_BACKUP_CANNOT_DETERMINE_CONFIG_FILE_LOCATION.
- get(getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Read the Directory Server configuration file and put it in the archive.
- byte[] buffer = new byte[8192];
- FileInputStream inputStream = null;
- try
- {
- File f = new File(configFile);
-
- ZipEntry zipEntry = new ZipEntry(f.getName());
- zipStream.putNextEntry(zipEntry);
-
- inputStream = new FileInputStream(f);
- while (true)
- {
- int bytesRead = inputStream.read(buffer);
- if (bytesRead < 0 || backupConfig.isCancelled())
- {
- break;
- }
-
- if (hash)
- {
- if (signHash)
- {
- mac.update(buffer, 0, bytesRead);
- }
- else
- {
- digest.update(buffer, 0, bytesRead);
- }
- }
-
- zipStream.write(buffer, 0, bytesRead);
- }
-
- inputStream.close();
- zipStream.closeEntry();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- StaticUtils.close(inputStream, zipStream);
-
- message = ERR_CONFIG_BACKUP_CANNOT_BACKUP_CONFIG_FILE.get(
- configFile, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // If an archive directory exists, then add its contents as well.
- try
- {
- File archiveDirectory = new File(new File(configFile).getParent(),
- CONFIG_ARCHIVE_DIR_NAME);
- if (archiveDirectory.exists())
- {
- for (File archiveFile : archiveDirectory.listFiles())
- {
- ZipEntry zipEntry = new ZipEntry(CONFIG_ARCHIVE_DIR_NAME +
- File.separator +
- archiveFile.getName());
- zipStream.putNextEntry(zipEntry);
- inputStream = new FileInputStream(archiveFile);
- while (true)
- {
- int bytesRead = inputStream.read(buffer);
- if (bytesRead < 0 || backupConfig.isCancelled())
- {
- break;
- }
-
- if (hash)
- {
- if (signHash)
- {
- mac.update(buffer, 0, bytesRead);
- }
- else
- {
- digest.update(buffer, 0, bytesRead);
- }
- }
-
- zipStream.write(buffer, 0, bytesRead);
- }
-
- inputStream.close();
- zipStream.closeEntry();
- }
- }
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- StaticUtils.close(inputStream, zipStream);
-
- message = ERR_CONFIG_BACKUP_CANNOT_BACKUP_ARCHIVED_CONFIGS.get(
- configFile, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // We're done writing the file, so close the zip stream (which should also
- // close the underlying stream).
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_CONFIG_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get(
- filename, backupDirectory.getPath(), getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // Get the digest or MAC bytes if appropriate.
- byte[] digestBytes = null;
- byte[] macBytes = null;
- if (hash)
- {
- if (signHash)
- {
- macBytes = mac.doFinal();
- }
- else
- {
- digestBytes = digest.digest();
- }
- }
-
-
- // Create the backup info structure for this backup and add it to the backup
- // directory.
- // FIXME -- Should I use the date from when I started or finished?
- BackupInfo backupInfo = new BackupInfo(backupDirectory, backupID,
- new Date(), false, compress,
- encrypt, digestBytes, macBytes,
- null, backupProperties);
-
- try
- {
- backupDirectory.addBackup(backupInfo);
- backupDirectory.writeBackupDirectoryDescriptor();
- }
- catch (Exception e)
- {
- logger.traceException(e);
-
- message = ERR_CONFIG_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
- backupDirectory.getDescriptorPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // Remove the backup if this operation was cancelled since the
- // backup may be incomplete
- if (backupConfig.isCancelled())
- {
- removeBackup(backupDirectory, backupID);
- }
+ new BackupManager(getBackendID()).createBackup(this, backupConfig);
}
/** {@inheritDoc} */
@Override
- public void removeBackup(BackupDirectory backupDirectory,
- String backupID)
- throws DirectoryException
+ public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
{
- // NYI
+ new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID);
}
/** {@inheritDoc} */
@Override
- public void restoreBackup(RestoreConfig restoreConfig)
- throws DirectoryException
+ public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
{
- // First, make sure that the requested backup exists.
- BackupDirectory backupDirectory = restoreConfig.getBackupDirectory();
- String backupPath = backupDirectory.getPath();
- String backupID = restoreConfig.getBackupID();
- BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
- if (backupInfo == null)
- {
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_NO_SUCH_BACKUP.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
-
- // Read the backup info structure to determine the name of the file that
- // contains the archive. Then make sure that file exists.
- String backupFilename =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME);
- if (backupFilename == null)
- {
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_NO_BACKUP_FILE.get(backupID, backupPath);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- File backupFile = new File(backupPath + File.separator + backupFilename);
- try
- {
- if (! backupFile.exists())
- {
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_NO_SUCH_FILE.get(backupID, backupFile.getPath());
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
- catch (DirectoryException de)
- {
- throw de;
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_CHECK_FOR_ARCHIVE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // If the backup is hashed, then we need to get the message digest to use
- // to verify it.
- byte[] unsignedHash = backupInfo.getUnsignedHash();
- MessageDigest digest = null;
- if (unsignedHash != null)
- {
- String digestAlgorithm =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM);
- if (digestAlgorithm == null)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_UNKNOWN_DIGEST.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- digest = DirectoryServer.getCryptoManager().getMessageDigest(
- digestAlgorithm);
- }
- catch (Exception e)
- {
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_CANNOT_GET_DIGEST.get(backupID, digestAlgorithm);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // If the backup is signed, then we need to get the MAC to use to verify it.
- byte[] signedHash = backupInfo.getSignedHash();
- Mac mac = null;
- if (signedHash != null)
- {
- String macKeyID =
- backupInfo.getBackupProperty(BACKUP_PROPERTY_MAC_KEY_ID);
- if (macKeyID == null)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_UNKNOWN_MAC.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
-
- try
- {
- mac = DirectoryServer.getCryptoManager().getMacEngine(macKeyID);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_GET_MAC.get(
- backupID, macKeyID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Create the input stream that will be used to read the backup file. At
- // its core, it will be a file input stream.
- InputStream inputStream;
- try
- {
- inputStream = new FileInputStream(backupFile);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_OPEN_BACKUP_FILE.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- // If the backup is encrypted, then we need to wrap the file input stream
- // in a cipher input stream.
- if (backupInfo.isEncrypted())
- {
- try
- {
- inputStream = DirectoryServer.getCryptoManager()
- .getCipherInputStream(inputStream);
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_GET_CIPHER.get(
- backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
- // Now wrap the resulting input stream in a zip stream so that we can read
- // its contents. We don't need to worry about whether to use compression or
- // not because it will be handled automatically.
- ZipInputStream zipStream = new ZipInputStream(inputStream);
-
-
- // Determine whether we should actually do the restore, or if we should just
- // try to verify the archive. If we are going to actually do the restore,
- // then create a directory and move the existing config files there so that
- // they can be restored in case something goes wrong.
- String configFilePath =
- ((ConfigFileHandler) DirectoryServer.getConfigHandler()).configFile;
- File configFile = new File(configFilePath);
- File configDir = configFile.getParentFile();
- String configDirPath = configDir.getPath();
- String backupDirPath = null;
- File configBackupDir = null;
- boolean verifyOnly = restoreConfig.verifyOnly();
- if (! verifyOnly)
- {
- // Create a new directory to hold the current config files.
- try
- {
- if (configDir.exists())
- {
- String configBackupDirPath = configDirPath + ".save";
- backupDirPath = configBackupDirPath;
- configBackupDir = new File(backupDirPath);
- if (configBackupDir.exists())
- {
- int i=2;
- while (true)
- {
- backupDirPath = configBackupDirPath + i;
- configBackupDir = new File(backupDirPath);
- if (configBackupDir.exists())
- {
- i++;
- }
- else
- {
- break;
- }
- }
- }
-
- configBackupDir.mkdirs();
- moveFile(configFile, configBackupDir);
-
- File archiveDirectory = new File(configDir, CONFIG_ARCHIVE_DIR_NAME);
- if (archiveDirectory.exists())
- {
- File archiveBackupPath = new File(configBackupDir,
- CONFIG_ARCHIVE_DIR_NAME);
- archiveDirectory.renameTo(archiveBackupPath);
- }
- }
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_BACKUP_EXISTING_CONFIG.
- get(backupID, configDirPath, backupDirPath, getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
- }
-
-
- // Create a new directory to hold the restored config files.
- try
- {
- configDir.mkdirs();
- }
- catch (Exception e)
- {
- // Try to restore the previous config directory if possible. This will
- // probably fail in this case, but try anyway.
- if (configBackupDir != null)
- {
- try
- {
- configBackupDir.renameTo(configDir);
- logger.info(NOTE_CONFIG_RESTORE_RESTORED_OLD_CONFIG, configDirPath);
- }
- catch (Exception e2)
- {
- logger.error(ERR_CONFIG_RESTORE_CANNOT_RESTORE_OLD_CONFIG, configBackupDir.getPath());
- }
- }
-
-
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_CREATE_CONFIG_DIRECTORY.get(
- backupID, configDirPath, getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Read through the archive file an entry at a time. For each entry, update
- // the digest or MAC if necessary, and if we're actually doing the restore,
- // then write the files out into the config directory.
- byte[] buffer = new byte[8192];
- while (true)
- {
- ZipEntry zipEntry;
- try
- {
- zipEntry = zipStream.getNextEntry();
- }
- catch (Exception e)
- {
- // Tell the user where the previous config was archived.
- if (configBackupDir != null)
- {
- logger.error(ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED, configBackupDir.getPath());
- }
-
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_GET_ZIP_ENTRY.get(
- backupID, backupFile.getPath(), stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
- if (zipEntry == null)
- {
- break;
- }
-
-
- // Get the filename for the zip entry and update the digest or MAC as
- // necessary.
- String fileName = zipEntry.getName();
- if (digest != null)
- {
- digest.update(getBytes(fileName));
- }
- if (mac != null)
- {
- mac.update(getBytes(fileName));
- }
-
-
- // If we're doing the restore, then create the output stream to write the
- // file.
- OutputStream outputStream = null;
- if (! verifyOnly)
- {
- File restoreFile = new File(configDirPath + File.separator + fileName);
- File parentDir = restoreFile.getParentFile();
-
- try
- {
- if (! parentDir.exists())
- {
- parentDir.mkdirs();
- }
-
- outputStream = new FileOutputStream(restoreFile);
- }
- catch (Exception e)
- {
- // Tell the user where the previous config was archived.
- if (configBackupDir != null)
- {
- logger.error(ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED, configBackupDir.getPath());
- }
-
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_CREATE_FILE.
- get(backupID, restoreFile.getAbsolutePath(),
- stackTraceToSingleLineString(e));
- throw new DirectoryException(
- DirectoryServer.getServerErrorResultCode(), message,
- e);
- }
- }
-
-
- // Read the contents of the file and update the digest or MAC as
- // necessary. If we're actually restoring it, then write it into the
- // new config directory.
- try
- {
- while (true)
- {
- int bytesRead = zipStream.read(buffer);
- if (bytesRead < 0)
- {
- // We've reached the end of the entry.
- break;
- }
-
-
- // Update the digest or MAC if appropriate.
- if (digest != null)
- {
- digest.update(buffer, 0, bytesRead);
- }
-
- if (mac != null)
- {
- mac.update(buffer, 0, bytesRead);
- }
-
-
- // Write the data to the output stream if appropriate.
- if (outputStream != null)
- {
- outputStream.write(buffer, 0, bytesRead);
- }
- }
-
-
- // We're at the end of the file so close the output stream if we're
- // writing it.
- if (outputStream != null)
- {
- outputStream.close();
- }
- }
- catch (Exception e)
- {
- // Tell the user where the previous config was archived.
- if (configBackupDir != null)
- {
- logger.error(ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED, configBackupDir.getPath());
- }
-
- LocalizableMessage message = ERR_CONFIG_RESTORE_CANNOT_PROCESS_ARCHIVE_FILE.get(
- backupID, fileName, stackTraceToSingleLineString(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
- }
-
-
- // Close the zip stream since we don't need it anymore.
- try
- {
- zipStream.close();
- }
- catch (Exception e)
- {
- LocalizableMessage message = ERR_CONFIG_RESTORE_ERROR_ON_ZIP_STREAM_CLOSE.get(
- backupID, backupFile.getPath(), getExceptionMessage(e));
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message, e);
- }
-
-
- // At this point, we should be done with the contents of the ZIP file and
- // the restore should be complete. If we were generating a digest or MAC,
- // then make sure it checks out.
- if (digest != null)
- {
- byte[] calculatedHash = digest.digest();
- if (Arrays.equals(calculatedHash, unsignedHash))
- {
- logger.info(NOTE_CONFIG_RESTORE_UNSIGNED_HASH_VALID);
- }
- else
- {
- // Tell the user where the previous config was archived.
- if (configBackupDir != null)
- {
- logger.error(ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED, configBackupDir.getPath());
- }
-
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_UNSIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
- if (mac != null)
- {
- byte[] calculatedSignature = mac.doFinal();
- if (Arrays.equals(calculatedSignature, signedHash))
- {
- logger.info(NOTE_CONFIG_RESTORE_SIGNED_HASH_VALID);
- }
- else
- {
- // Tell the user where the previous config was archived.
- if (configBackupDir != null)
- {
- logger.error(ERR_CONFIG_RESTORE_OLD_CONFIG_SAVED, configBackupDir.getPath());
- }
-
- LocalizableMessage message =
- ERR_CONFIG_RESTORE_SIGNED_HASH_INVALID.get(backupID);
- throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
- message);
- }
- }
-
-
- // If we are just verifying the archive, then we're done.
- if (verifyOnly)
- {
- logger.info(NOTE_CONFIG_RESTORE_VERIFY_SUCCESSFUL, backupID, backupPath);
- return;
- }
-
-
- // If we've gotten here, then the archive was restored successfully. Get
- // rid of the temporary copy we made of the previous config directory and
- // exit.
- if (configBackupDir != null)
- {
- recursiveDelete(configBackupDir);
- }
-
- logger.info(NOTE_CONFIG_RESTORE_SUCCESSFUL, backupID, backupPath);
+ new BackupManager(getBackendID()).restoreBackup(this, restoreConfig);
}
/** {@inheritDoc} */
@@ -2857,4 +2056,66 @@
throw new UnsupportedOperationException("Operation not supported.");
}
+ /** {@inheritDoc} */
+ @Override
+ public File getDirectory()
+ {
+ return getConfigFileInBackendContext().getParentFile();
+ }
+
+ private File getConfigFileInBackendContext()
+ {
+ // This may seem a little weird, but in some context, we only have access to
+ // this class as a backend and not as the config handler. We need it as a
+ // config handler to determine the path to the config file, so we can get
+ // that from the Directory Server object.
+ return new File(((ConfigFileHandler) DirectoryServer.getConfigHandler()).configFile);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ListIterator<Path> getFilesToBackup()
+ {
+ final List<Path> files = new ArrayList<>();
+
+ // the main config file
+ File theConfigFile = getConfigFileInBackendContext();
+ files.add(theConfigFile.toPath());
+
+ // the files in archive directory
+ File archiveDirectory = new File(getDirectory(), CONFIG_ARCHIVE_DIR_NAME);
+ if (archiveDirectory.exists())
+ {
+ for (File archiveFile : archiveDirectory.listFiles())
+ {
+ files.add(archiveFile.toPath());
+ }
+ }
+
+ return files.listIterator();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isDirectRestore()
+ {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Path beforeRestore() throws DirectoryException
+ {
+ // save current config files to a save directory
+ return BackupManager.saveCurrentFilesToDirectory(this, getBackendID());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException
+ {
+ // restore was successful, delete save directory
+ StaticUtils.recursiveDelete(saveDirectory.toFile());
+ }
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/util/BackupManager.java b/opendj-server-legacy/src/main/java/org/opends/server/util/BackupManager.java
new file mode 100644
index 0000000..9949a45
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/util/BackupManager.java
@@ -0,0 +1,1637 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2006-2009 Sun Microsystems, Inc.
+ * Portions Copyright 2013-2015 ForgeRock AS.
+ */
+package org.opends.server.util;
+
+import static java.util.Collections.*;
+
+import static org.opends.messages.BackendMessages.*;
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javax.crypto.Mac;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.util.Pair;
+import org.opends.server.api.Backupable;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.BackupInfo;
+import org.opends.server.types.CryptoManager;
+import org.opends.server.types.CryptoManagerException;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.RestoreConfig;
+
+/**
+ * A backup manager for any entity that is backupable (backend, storage).
+ *
+ * @see {@link Backupable}
+ */
+public class BackupManager
+{
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+ /**
+ * The common prefix for archive files.
+ */
+ private static final String BACKUP_BASE_FILENAME = "backup-";
+
+ /**
+ * The name of the property that holds the name of the latest log file
+ * at the time the backup was created.
+ */
+ private static final String PROPERTY_LAST_LOGFILE_NAME = "last_logfile_name";
+
+ /**
+ * The name of the property that holds the size of the latest log file
+ * at the time the backup was created.
+ */
+ private static final String PROPERTY_LAST_LOGFILE_SIZE = "last_logfile_size";
+
+
+ /**
+ * The name of the entry in an incremental backup archive file
+ * containing a list of log files that are unchanged since the
+ * previous backup.
+ */
+ private static final String ZIPENTRY_UNCHANGED_LOGFILES = "unchanged.txt";
+
+ /**
+ * The name of a dummy entry in the backup archive file that will act
+ * as a placeholder in case a backup is done on an empty backend.
+ */
+ private static final String ZIPENTRY_EMPTY_PLACEHOLDER = "empty.placeholder";
+
+
+ /**
+ * The backend ID.
+ */
+ private final String backendID;
+
+ /**
+ * Construct a backup manager for a backend.
+ *
+ * @param backendID
+ * The ID of the backend instance for which a backup manager is
+ * required.
+ */
+ public BackupManager(String backendID)
+ {
+ this.backendID = backendID;
+ }
+
+ /** A cryptographic engine to use for backup creation or restore. */
+ private static abstract class CryptoEngine
+ {
+ final CryptoManager cryptoManager;
+ final boolean shouldEncrypt;
+
+ /** Creates a crypto engine for archive creation. */
+ static CryptoEngine forCreation(BackupConfig backupConfig, NewBackupParams backupParams)
+ throws DirectoryException {
+ if (backupConfig.hashData())
+ {
+ if (backupConfig.signHash())
+ {
+ return new MacCryptoEngine(backupConfig, backupParams);
+ }
+ else
+ {
+ return new DigestCryptoEngine(backupConfig, backupParams);
+ }
+ }
+ else
+ {
+ return new NoHashCryptoEngine(backupConfig.encryptData());
+ }
+ }
+
+ /** Creates a crypto engine for archive restore. */
+ static CryptoEngine forRestore(BackupInfo backupInfo)
+ throws DirectoryException {
+ boolean hasSignedHash = backupInfo.getSignedHash() != null;
+ boolean hasHashData = hasSignedHash || backupInfo.getUnsignedHash() != null;
+ if (hasHashData)
+ {
+ if (hasSignedHash)
+ {
+ return new MacCryptoEngine(backupInfo);
+ }
+ else
+ {
+ return new DigestCryptoEngine(backupInfo);
+ }
+ }
+ else
+ {
+ return new NoHashCryptoEngine(backupInfo.isEncrypted());
+ }
+ }
+
+ CryptoEngine(boolean shouldEncrypt)
+ {
+ cryptoManager = DirectoryServer.getCryptoManager();
+ this.shouldEncrypt = shouldEncrypt;
+ }
+
+ /** Indicates if data is encrypted. */
+ final boolean shouldEncrypt() {
+ return shouldEncrypt;
+ }
+
+ /** Indicates if hashed data is signed. */
+ boolean hasSignedHash() {
+ return false;
+ }
+
+ /** Update the hash with the provided string. */
+ abstract void updateHashWith(String s);
+
+ /** Update the hash with the provided buffer. */
+ abstract void updateHashWith(byte[] buffer, int offset, int len);
+
+ /** Generates the hash bytes. */
+ abstract byte[] generateBytes();
+
+ /** Returns the error message to use in case of check failure. */
+ abstract LocalizableMessage getErrorMessageForCheck(String backupID);
+
+ /** Check that generated hash is equal to the provided hash. */
+ final void check(byte[] hash, String backupID) throws DirectoryException
+ {
+ byte[] bytes = generateBytes();
+ if (bytes != null && !Arrays.equals(bytes, hash))
+ {
+ LocalizableMessage message = getErrorMessageForCheck(backupID);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
+ }
+ }
+
+ /** Wraps an output stream in a cipher output stream if encryption is required. */
+ final OutputStream encryptOutput(OutputStream output) throws DirectoryException
+ {
+ if (!shouldEncrypt())
+ {
+ return output;
+ }
+ try
+ {
+ return cryptoManager.getCipherOutputStream(output);
+ }
+ catch (CryptoManagerException e)
+ {
+ logger.traceException(e);
+ StaticUtils.close(output);
+ LocalizableMessage message = ERR_BACKUP_CANNOT_GET_CIPHER.get(stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ }
+
+ /** Wraps an input stream in a cipher input stream if encryption is required. */
+ final InputStream encryptInput(InputStream inputStream) throws DirectoryException
+ {
+ if (!shouldEncrypt)
+ {
+ return inputStream;
+ }
+
+ try
+ {
+ return cryptoManager.getCipherInputStream(inputStream);
+ }
+ catch (CryptoManagerException e)
+ {
+ logger.traceException(e);
+ StaticUtils.close(inputStream);
+ LocalizableMessage message = ERR_BACKUP_CANNOT_GET_CIPHER.get(stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ }
+
+ }
+
+ /** Represents the cryptographic engine with no hash used for a backup. */
+ private static final class NoHashCryptoEngine extends CryptoEngine
+ {
+
+ NoHashCryptoEngine(boolean shouldEncrypt)
+ {
+ super(shouldEncrypt);
+ }
+
+ @Override
+ void updateHashWith(String s)
+ {
+ // nothing to do
+ }
+
+ @Override
+ void updateHashWith(byte[] buffer, int offset, int len)
+ {
+ // nothing to do
+ }
+
+ @Override
+ byte[] generateBytes()
+ {
+ return null;
+ }
+
+ @Override
+ LocalizableMessage getErrorMessageForCheck(String backupID)
+ {
+ // check never fails because bytes are always null
+ return null;
+ }
+
+ }
+
+ /**
+ * Represents the cryptographic engine with signed hash.
+ */
+ private static final class MacCryptoEngine extends CryptoEngine
+ {
+ private Mac mac;
+
+ /** Constructor for backup creation. */
+ private MacCryptoEngine(BackupConfig backupConfig, NewBackupParams backupParams) throws DirectoryException
+ {
+ super(backupConfig.encryptData());
+
+ String macKeyID = null;
+ try
+ {
+ macKeyID = cryptoManager.getMacEngineKeyEntryID();
+ backupParams.putProperty(BACKUP_PROPERTY_MAC_KEY_ID, macKeyID);
+ }
+ catch (CryptoManagerException e)
+ {
+ LocalizableMessage message = ERR_BACKUP_CANNOT_GET_MAC_KEY_ID.get(backupParams.backupID,
+ stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ retrieveMacEngine(macKeyID);
+ }
+
+ /** Constructor for backup restore. */
+ private MacCryptoEngine(BackupInfo backupInfo) throws DirectoryException
+ {
+ super(backupInfo.isEncrypted());
+ HashMap<String,String> backupProperties = backupInfo.getBackupProperties();
+ String macKeyID = backupProperties.get(BACKUP_PROPERTY_MAC_KEY_ID);
+ retrieveMacEngine(macKeyID);
+ }
+
+ private void retrieveMacEngine(String macKeyID) throws DirectoryException
+ {
+ try
+ {
+ mac = cryptoManager.getMacEngine(macKeyID);
+ }
+ catch (Exception e)
+ {
+ LocalizableMessage message = ERR_BACKUP_CANNOT_GET_MAC.get(macKeyID, stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ void updateHashWith(String s)
+ {
+ mac.update(getBytes(s));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ void updateHashWith(byte[] buffer, int offset, int len)
+ {
+ mac.update(buffer, offset, len);
+ }
+
+ @Override
+ byte[] generateBytes()
+ {
+ return mac.doFinal();
+ }
+
+ @Override
+ boolean hasSignedHash()
+ {
+ return true;
+ }
+
+ @Override
+ LocalizableMessage getErrorMessageForCheck(String backupID)
+ {
+ return ERR_BACKUP_SIGNED_HASH_ERROR.get(backupID);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "MacCryptoEngine [mac=" + mac + "]";
+ }
+
+ }
+
+ /** Represents the cryptographic engine with unsigned hash used for a backup. */
+ private static final class DigestCryptoEngine extends CryptoEngine
+ {
+ private final MessageDigest digest;
+
+ /** Constructor for backup creation. */
+ private DigestCryptoEngine(BackupConfig backupConfig, NewBackupParams backupParams) throws DirectoryException
+ {
+ super(backupConfig.encryptData());
+ String digestAlgorithm = cryptoManager.getPreferredMessageDigestAlgorithm();
+ backupParams.putProperty(BACKUP_PROPERTY_DIGEST_ALGORITHM, digestAlgorithm);
+ digest = retrieveMessageDigest(digestAlgorithm);
+ }
+
+ /** Constructor for backup restore. */
+ private DigestCryptoEngine(BackupInfo backupInfo) throws DirectoryException
+ {
+ super(backupInfo.isEncrypted());
+ HashMap<String, String> backupProperties = backupInfo.getBackupProperties();
+ String digestAlgorithm = backupProperties.get(BACKUP_PROPERTY_DIGEST_ALGORITHM);
+ digest = retrieveMessageDigest(digestAlgorithm);
+ }
+
+ private MessageDigest retrieveMessageDigest(String digestAlgorithm) throws DirectoryException
+ {
+ try
+ {
+ return cryptoManager.getMessageDigest(digestAlgorithm);
+ }
+ catch (Exception e)
+ {
+ LocalizableMessage message =
+ ERR_BACKUP_CANNOT_GET_DIGEST.get(digestAlgorithm, stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateHashWith(String s)
+ {
+ digest.update(getBytes(s));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateHashWith(byte[] buffer, int offset, int len)
+ {
+ digest.update(buffer, offset, len);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public byte[] generateBytes()
+ {
+ return digest.digest();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ LocalizableMessage getErrorMessageForCheck(String backupID)
+ {
+ return ERR_BACKUP_UNSIGNED_HASH_ERROR.get(backupID);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "DigestCryptoEngine [digest=" + digest + "]";
+ }
+
+ }
+
+ /**
+ * Contains all parameters for creation of a new backup.
+ */
+ private static final class NewBackupParams
+ {
+ final String backupID;
+ final BackupDirectory backupDir;
+ final HashMap<String,String> backupProperties;
+
+ final boolean shouldCompress;
+
+ final boolean isIncremental;
+ final String incrementalBaseID;
+ final BackupInfo baseBackupInfo;
+
+ NewBackupParams(BackupConfig backupConfig) throws DirectoryException
+ {
+ backupID = backupConfig.getBackupID();
+ backupDir = backupConfig.getBackupDirectory();
+ backupProperties = new HashMap<String,String>();
+ shouldCompress = backupConfig.compressData();
+
+ incrementalBaseID = retrieveIncrementalBaseID(backupConfig);
+ isIncremental = incrementalBaseID != null;
+ baseBackupInfo = isIncremental ? getBackupInfo(backupDir, incrementalBaseID) : null;
+ }
+
+ private String retrieveIncrementalBaseID(BackupConfig backupConfig)
+ {
+ String id = null;
+ if (backupConfig.isIncremental())
+ {
+ if (backupConfig.getIncrementalBaseID() == null && backupDir.getLatestBackup() != null)
+ {
+ // The default is to use the latest backup as base.
+ id = backupDir.getLatestBackup().getBackupID();
+ }
+ else
+ {
+ id = backupConfig.getIncrementalBaseID();
+ }
+
+ if (id == null)
+ {
+ // No incremental backup ID: log a message informing that a backup
+ // could not be found and that a normal backup will be done.
+ logger.warn(WARN_BACKUPDB_INCREMENTAL_NOT_FOUND_DOING_NORMAL, backupDir.getPath());
+ }
+ }
+ return id;
+ }
+
+ void putProperty(String name, String value) {
+ backupProperties.put(name, value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "BackupCreationParams [backupID=" + backupID + ", backupDir=" + backupDir.getPath() + "]";
+ }
+
+ }
+
+ /** Represents a new backup archive. */
+ private static final class NewBackupArchive {
+
+ private final String archiveFilename;
+
+ private String latestFileName;
+ private long latestFileSize;
+
+ private final HashSet<String> dependencies;
+
+ private final String backendID;
+ private final NewBackupParams newBackupParams;
+ private final CryptoEngine cryptoEngine;
+
+ NewBackupArchive(String backendID, NewBackupParams backupParams, CryptoEngine crypt)
+ {
+ this.backendID = backendID;
+ this.newBackupParams = backupParams;
+ this.cryptoEngine = crypt;
+ dependencies = new HashSet<String>();
+ if (backupParams.isIncremental)
+ {
+ HashMap<String,String> properties = backupParams.baseBackupInfo.getBackupProperties();
+ latestFileName = properties.get(PROPERTY_LAST_LOGFILE_NAME);
+ latestFileSize = Long.parseLong(properties.get(PROPERTY_LAST_LOGFILE_SIZE));
+ }
+ archiveFilename = BACKUP_BASE_FILENAME + backendID + "-" + backupParams.backupID;
+ }
+
+ String getArchiveFilename()
+ {
+ return archiveFilename;
+ }
+
+ String getBackendID()
+ {
+ return backendID;
+ }
+
+ String getBackupID()
+ {
+ return newBackupParams.backupID;
+ }
+
+ String getBackupPath() {
+ return newBackupParams.backupDir.getPath();
+ }
+
+ void addBaseBackupAsDependency() {
+ dependencies.add(newBackupParams.baseBackupInfo.getBackupID());
+ }
+
+ void updateBackupDirectory() throws DirectoryException
+ {
+ BackupInfo backupInfo = createDescriptorForBackup();
+ try
+ {
+ newBackupParams.backupDir.addBackup(backupInfo);
+ newBackupParams.backupDir.writeBackupDirectoryDescriptor();
+ }
+ catch (Exception e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
+ newBackupParams.backupDir.getDescriptorPath(), stackTraceToSingleLineString(e)),
+ e);
+ }
+ }
+
+ /** Create a descriptor for the backup. */
+ private BackupInfo createDescriptorForBackup()
+ {
+ byte[] bytes = cryptoEngine.generateBytes();
+ byte[] digestBytes = cryptoEngine.hasSignedHash() ? null : bytes;
+ byte[] macBytes = cryptoEngine.hasSignedHash() ? bytes : null;
+ newBackupParams.putProperty(PROPERTY_LAST_LOGFILE_NAME, latestFileName);
+ newBackupParams.putProperty(PROPERTY_LAST_LOGFILE_SIZE, String.valueOf(latestFileSize));
+ return new BackupInfo(
+ newBackupParams.backupDir, newBackupParams.backupID, new Date(), newBackupParams.isIncremental,
+ newBackupParams.shouldCompress, cryptoEngine.shouldEncrypt(), digestBytes, macBytes,
+ dependencies, newBackupParams.backupProperties);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "NewArchive [archive file=" + archiveFilename + ", latestFileName=" + latestFileName
+ + ", backendID=" + backendID + "]";
+ }
+
+ }
+
+ /** Represents an existing backup archive. */
+ private static final class ExistingBackupArchive {
+
+ private final String backupID;
+ private final BackupDirectory backupDir;
+ private final BackupInfo backupInfo;
+ private final CryptoEngine cryptoEngine;
+ private final File archiveFile;
+
+ ExistingBackupArchive(String backupID, BackupDirectory backupDir) throws DirectoryException
+ {
+ this.backupID = backupID;
+ this.backupDir = backupDir;
+ this.backupInfo = BackupManager.getBackupInfo(backupDir, backupID);
+ this.cryptoEngine = CryptoEngine.forRestore(backupInfo);
+ this.archiveFile = BackupManager.retrieveArchiveFile(backupInfo, backupDir.getPath());
+ }
+
+ File getArchiveFile()
+ {
+ return archiveFile;
+ }
+
+ BackupInfo getBackupInfo() {
+ return backupInfo;
+ }
+
+ String getBackupID()
+ {
+ return backupID;
+ }
+
+ CryptoEngine getCryptoEngine()
+ {
+ return cryptoEngine;
+ }
+
+ /**
+ * Obtains a list of the dependencies of this backup in order from
+ * the oldest (the full backup), to the most recent.
+ *
+ * @return A list of dependent backups.
+ * @throws DirectoryException If a Directory Server error occurs.
+ */
+ List<BackupInfo> getBackupDependencies() throws DirectoryException
+ {
+ List<BackupInfo> dependencies = new ArrayList<BackupInfo>();
+ BackupInfo currentBackupInfo = backupInfo;
+ while (currentBackupInfo != null && !currentBackupInfo.getDependencies().isEmpty())
+ {
+ String backupID = currentBackupInfo.getDependencies().iterator().next();
+ currentBackupInfo = backupDir.getBackupInfo(backupID);
+ if (currentBackupInfo != null)
+ {
+ dependencies.add(currentBackupInfo);
+ }
+ }
+ Collections.reverse(dependencies);
+ return dependencies;
+ }
+
+ boolean hasDependencies()
+ {
+ return !backupInfo.getDependencies().isEmpty();
+ }
+
+ /** Removes the archive from file system. */
+ boolean removeArchive() throws DirectoryException
+ {
+ try
+ {
+ backupDir.removeBackup(backupID);
+ backupDir.writeBackupDirectoryDescriptor();
+ }
+ catch (ConfigException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), e.getMessageObject());
+ }
+ catch (Exception e)
+ {
+ logger.traceException(e);
+ LocalizableMessage message = ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR.get(
+ backupDir.getDescriptorPath(), stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+
+ return archiveFile.delete();
+ }
+
+ }
+
+ /** Represents a writer of a backup archive. */
+ private static final class BackupArchiveWriter implements Closeable {
+
+ private final ZipOutputStream zipOutputStream;
+ private final NewBackupArchive archive;
+ private final CryptoEngine cryptoEngine;
+
+ BackupArchiveWriter(NewBackupArchive archive) throws DirectoryException
+ {
+ this.archive = archive;
+ this.cryptoEngine = archive.cryptoEngine;
+ this.zipOutputStream = open(archive.getBackupPath(), archive.getArchiveFilename());
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ StaticUtils.close(zipOutputStream);
+ }
+
+ /**
+ * Writes the provided file to a new entry in the archive.
+ *
+ * @param file
+ * The file to be written.
+ * @param cryptoMethod
+ * The cryptographic method for the written data.
+ * @param backupConfig
+ * The configuration, used to know if operation is cancelled.
+ *
+ * @return The number of bytes written from the file.
+ * @throws FileNotFoundException If the file to be archived does not exist.
+ * @throws IOException If an I/O error occurs while archiving the file.
+ */
+ long writeFile(Path file, String relativePath, CryptoEngine cryptoMethod, BackupConfig backupConfig)
+ throws IOException, FileNotFoundException
+ {
+ ZipEntry zipEntry = new ZipEntry(relativePath);
+ zipOutputStream.putNextEntry(zipEntry);
+
+ cryptoMethod.updateHashWith(relativePath);
+
+ InputStream inputStream = null;
+ long totalBytesRead = 0;
+ try {
+ inputStream = new FileInputStream(file.toFile());
+ byte[] buffer = new byte[8192];
+ int bytesRead = inputStream.read(buffer);
+ while (bytesRead > 0 && !backupConfig.isCancelled())
+ {
+ cryptoMethod.updateHashWith(buffer, 0, bytesRead);
+ zipOutputStream.write(buffer, 0, bytesRead);
+ totalBytesRead += bytesRead;
+ bytesRead = inputStream.read(buffer);
+ }
+ }
+ finally {
+ StaticUtils.close(inputStream);
+ }
+
+ zipOutputStream.closeEntry();
+ logger.info(NOTE_BACKUP_ARCHIVED_FILE, zipEntry.getName());
+ return totalBytesRead;
+ }
+
+ /**
+ * Write a list of strings to an entry in the archive.
+ *
+ * @param stringList
+ * A list of strings to be written. The strings must not
+ * contain newlines.
+ * @param fileName
+ * The name of the zip entry to be written.
+ * @param cryptoMethod
+ * The cryptographic method for the written data.
+ * @throws IOException
+ * If an I/O error occurs while writing the archive entry.
+ */
+ void writeStrings(List<String> stringList, String fileName, CryptoEngine cryptoMethod)
+ throws IOException
+ {
+ ZipEntry zipEntry = new ZipEntry(fileName);
+ zipOutputStream.putNextEntry(zipEntry);
+
+ cryptoMethod.updateHashWith(fileName);
+
+ Writer writer = new OutputStreamWriter(zipOutputStream);
+ for (String s : stringList)
+ {
+ cryptoMethod.updateHashWith(s);
+ writer.write(s);
+ writer.write(EOL);
+ }
+ writer.flush();
+ zipOutputStream.closeEntry();
+ }
+
+ /** Writes a empty placeholder entry into the archive. */
+ void writeEmptyPlaceHolder() throws DirectoryException
+ {
+ try
+ {
+ ZipEntry emptyPlaceholder = new ZipEntry(ZIPENTRY_EMPTY_PLACEHOLDER);
+ zipOutputStream.putNextEntry(emptyPlaceholder);
+ }
+ catch (IOException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(ZIPENTRY_EMPTY_PLACEHOLDER, archive.getBackupID(),
+ stackTraceToSingleLineString(e)),
+ e);
+ }
+ }
+
+ /**
+ * Writes the files that are unchanged from the base backup (for an
+ * incremental backup only).
+ * <p>
+ * The unchanged files names are listed in the "unchanged.txt" file, which
+ * is put in the archive.
+ *
+ */
+ void writeUnchangedFiles(Path rootDirectory, ListIterator<Path> files, BackupConfig backupConfig)
+ throws DirectoryException
+ {
+ List<String> unchangedFilenames = new ArrayList<String>();
+ while (files.hasNext() && !backupConfig.isCancelled())
+ {
+ Path file = files.next();
+ String relativePath = rootDirectory.relativize(file).toString();
+ int cmp = relativePath.compareTo(archive.latestFileName);
+ if (cmp > 0 || (cmp == 0 && file.toFile().length() != archive.latestFileSize))
+ {
+ files.previous();
+ break;
+ }
+ logger.info(NOTE_BACKUP_FILE_UNCHANGED, relativePath);
+ unchangedFilenames.add(relativePath);
+ }
+
+ if (!unchangedFilenames.isEmpty())
+ {
+ writeUnchangedFilenames(unchangedFilenames);
+ }
+ }
+
+ /** Writes the list of unchanged files names in a file as new entry in the archive. */
+ private void writeUnchangedFilenames(List<String> unchangedList) throws DirectoryException
+ {
+ String zipEntryName = ZIPENTRY_UNCHANGED_LOGFILES;
+ try
+ {
+ writeStrings(unchangedList, zipEntryName, archive.cryptoEngine);
+ }
+ catch (IOException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(
+ DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(zipEntryName, archive.getBackupID(),
+ stackTraceToSingleLineString(e)), e);
+ }
+ archive.addBaseBackupAsDependency();
+ }
+
+ /**
+ * Writes the new files in the archive.
+ */
+ void writeChangedFiles(Path rootDirectory, ListIterator<Path> files, BackupConfig backupConfig)
+ throws DirectoryException
+ {
+ while (files.hasNext() && !backupConfig.isCancelled())
+ {
+ Path file = files.next();
+ String relativePath = rootDirectory.relativize(file).toString();
+ try
+ {
+ archive.latestFileSize = writeFile(file, relativePath, archive.cryptoEngine, backupConfig);
+ archive.latestFileName = relativePath;
+ }
+ catch (FileNotFoundException e)
+ {
+ // The file may have been deleted by a cleaner (i.e. for JE storage) since we started.
+ // The backupable entity is responsible for handling the changes through the files list iterator
+ logger.traceException(e);
+ }
+ catch (IOException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(relativePath, archive.getBackupID(),
+ stackTraceToSingleLineString(e)), e);
+ }
+ }
+ }
+
+ private ZipOutputStream open(String backupPath, String archiveFilename) throws DirectoryException
+ {
+ OutputStream output = openStream(backupPath, archiveFilename);
+ output = cryptoEngine.encryptOutput(output);
+ return openZipStream(output);
+ }
+
+ private OutputStream openStream(String backupPath, String archiveFilename) throws DirectoryException {
+ OutputStream output = null;
+ try
+ {
+ File archiveFile = new File(backupPath, archiveFilename);
+ int i = 1;
+ while (archiveFile.exists())
+ {
+ archiveFile = new File(backupPath, archiveFilename + "." + i);
+ i++;
+ }
+ output = new FileOutputStream(archiveFile, false);
+ archive.newBackupParams.putProperty(BACKUP_PROPERTY_ARCHIVE_FILENAME, archiveFilename);
+ return output;
+ }
+ catch (Exception e)
+ {
+ logger.traceException(e);
+ StaticUtils.close(output);
+ LocalizableMessage message = ERR_BACKUP_CANNOT_CREATE_ARCHIVE_FILE.
+ get(archiveFilename, backupPath, archive.getBackupID(), stackTraceToSingleLineString(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ }
+
+ /** Wraps the file output stream in a zip output stream. */
+ private ZipOutputStream openZipStream(OutputStream outputStream)
+ {
+ ZipOutputStream zipStream = new ZipOutputStream(outputStream);
+
+ zipStream.setComment(ERR_BACKUP_ZIP_COMMENT.get(DynamicConstants.PRODUCT_NAME, archive.getBackupID())
+ .toString());
+
+ if (archive.newBackupParams.shouldCompress)
+ {
+ zipStream.setLevel(Deflater.DEFAULT_COMPRESSION);
+ }
+ else
+ {
+ zipStream.setLevel(Deflater.NO_COMPRESSION);
+ }
+ return zipStream;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "BackupArchiveWriter [archive file=" + archive.getArchiveFilename() + ", backendId="
+ + archive.getBackendID() + "]";
+ }
+
+ }
+
+ /** Represents a reader of a backup archive. */
+ private static final class BackupArchiveReader {
+
+ private final CryptoEngine cryptoEngine;
+ private final File archiveFile;
+ private final String identifier;
+ private final BackupInfo backupInfo;
+
+ BackupArchiveReader(String identifier, ExistingBackupArchive archive)
+ {
+ this.identifier = identifier;
+ this.backupInfo = archive.getBackupInfo();
+ this.archiveFile = archive.getArchiveFile();
+ this.cryptoEngine = archive.getCryptoEngine();
+ }
+
+ BackupArchiveReader(String identifier, BackupInfo backupInfo, String backupDirectoryPath) throws DirectoryException
+ {
+ this.identifier = identifier;
+ this.backupInfo = backupInfo;
+ this.archiveFile = BackupManager.retrieveArchiveFile(backupInfo, backupDirectoryPath);
+ this.cryptoEngine = CryptoEngine.forRestore(backupInfo);
+ }
+
+ /**
+ * Obtains the set of files in a backup that are unchanged from its
+ * dependent backup or backups.
+ * <p>
+ * The file set is stored as as the first entry in the archive file.
+ *
+ * @return The set of files that are listed in "unchanged.txt" file
+ * of the archive.
+ * @throws DirectoryException
+ * If an error occurs.
+ */
+ Set<String> readUnchangedDependentFiles() throws DirectoryException
+ {
+ Set<String> hashSet = new HashSet<String>();
+ ZipInputStream zipStream = null;
+ try
+ {
+ zipStream = openZipStream();
+
+ // Iterate through the entries in the zip file.
+ ZipEntry zipEntry = zipStream.getNextEntry();
+ while (zipEntry != null)
+ {
+ // We are looking for the entry containing the list of unchanged files.
+ if (ZIPENTRY_UNCHANGED_LOGFILES.equals(zipEntry.getName()))
+ {
+ hashSet.addAll(readAllLines(zipStream));
+ break;
+ }
+ zipEntry = zipStream.getNextEntry();
+ }
+ return hashSet;
+ }
+ catch (IOException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), ERR_BACKUP_CANNOT_RESTORE.get(
+ identifier, stackTraceToSingleLineString(e)), e);
+ }
+ finally {
+ StaticUtils.close(zipStream);
+ }
+ }
+
+ /**
+ * Restore the provided list of files from the provided restore directory.
+ * @param restoreDir
+ * The target directory for restored files.
+ * @param filesToRestore
+ * The set of files to restore. If empty, all files in the archive
+ * are restored.
+ * @param restoreConfig
+ * The restore configuration, used to check for cancellation of
+ * this restore operation.
+ * @throws DirectoryException
+ * If an error occurs.
+ */
+ void restoreArchive(Path restoreDir, Set<String> filesToRestore, RestoreConfig restoreConfig, Backupable backupable)
+ throws DirectoryException
+ {
+ try
+ {
+ restoreArchive0(restoreDir, filesToRestore, restoreConfig, backupable);
+ }
+ catch (IOException e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_RESTORE.get(identifier, stackTraceToSingleLineString(e)), e);
+ }
+
+ // check the hash
+ byte[] hash = backupInfo.getUnsignedHash() != null ? backupInfo.getUnsignedHash() : backupInfo.getSignedHash();
+ cryptoEngine.check(hash, backupInfo.getBackupID());
+ }
+
+ private void restoreArchive0(Path restoreDir, Set<String> filesToRestore, RestoreConfig restoreConfig,
+ Backupable backupable) throws DirectoryException, IOException {
+
+ ZipInputStream zipStream = null;
+ try {
+ zipStream = openZipStream();
+
+ ZipEntry zipEntry = zipStream.getNextEntry();
+ while (zipEntry != null && !restoreConfig.isCancelled())
+ {
+ String zipEntryName = zipEntry.getName();
+
+ Pair<Boolean, ZipEntry> result = handleSpecialEntries(zipStream, zipEntryName);
+ if (result.getFirst()) {
+ zipEntry = result.getSecond();
+ continue;
+ }
+
+ boolean mustRestoreOnDisk = !restoreConfig.verifyOnly()
+ && (filesToRestore.isEmpty() || filesToRestore.contains(zipEntryName));
+
+ if (mustRestoreOnDisk)
+ {
+ restoreZipEntry(zipEntryName, zipStream, restoreDir, restoreConfig);
+ }
+ else
+ {
+ restoreZipEntryVirtual(zipEntryName, zipStream, restoreConfig);
+ }
+
+ zipEntry = zipStream.getNextEntry();
+ }
+ }
+ finally {
+ StaticUtils.close(zipStream);
+ }
+ }
+
+ /**
+ * Handle any special entry in the archive.
+ *
+ * @return the pair (true, zipEntry) if next entry was read, (false, null) otherwise
+ */
+ private Pair<Boolean, ZipEntry> handleSpecialEntries(ZipInputStream zipStream, String zipEntryName)
+ throws IOException
+ {
+ if (ZIPENTRY_EMPTY_PLACEHOLDER.equals(zipEntryName))
+ {
+ // the backup contains no files
+ return Pair.of(true, zipStream.getNextEntry());
+ }
+
+ if (ZIPENTRY_UNCHANGED_LOGFILES.equals(zipEntryName))
+ {
+ // This entry is treated specially. It is never restored,
+ // and its hash is computed on the strings, not the bytes.
+ cryptoEngine.updateHashWith(zipEntryName);
+ List<String> lines = readAllLines(zipStream);
+ for (String line : lines)
+ {
+ cryptoEngine.updateHashWith(line);
+ }
+ return Pair.of(true, zipStream.getNextEntry());
+ }
+ return Pair.of(false, null);
+ }
+
+ /**
+ * Restores a zip entry virtually (no actual write on disk).
+ */
+ private void restoreZipEntryVirtual(String zipEntryName, ZipInputStream zipStream, RestoreConfig restoreConfig)
+ throws FileNotFoundException, IOException
+ {
+ if (restoreConfig.verifyOnly())
+ {
+ logger.info(NOTE_BACKUP_VERIFY_FILE, zipEntryName);
+ }
+ cryptoEngine.updateHashWith(zipEntryName);
+ restoreFile(zipStream, null, restoreConfig);
+ }
+
+ /**
+ * Restores a zip entry with actual write on disk.
+ */
+ private void restoreZipEntry(String zipEntryName, ZipInputStream zipStream, Path restoreDir,
+ RestoreConfig restoreConfig) throws IOException, DirectoryException
+ {
+ OutputStream outputStream = null;
+ long totalBytesRead = 0;
+ try
+ {
+ Path fileToRestore = restoreDir.resolve(zipEntryName);
+ ensureFileCanBeRestored(fileToRestore);
+ outputStream = new FileOutputStream(fileToRestore.toFile());
+ cryptoEngine.updateHashWith(zipEntryName);
+ totalBytesRead = restoreFile(zipStream, outputStream, restoreConfig);
+ logger.info(NOTE_BACKUP_RESTORED_FILE, zipEntryName, totalBytesRead);
+ }
+ finally
+ {
+ StaticUtils.close(outputStream);
+ }
+ }
+
+ private void ensureFileCanBeRestored(Path fileToRestore) throws DirectoryException
+ {
+ Path parent = fileToRestore.getParent();
+ if (!Files.exists(parent))
+ {
+ try
+ {
+ Files.createDirectories(parent);
+ }
+ catch (IOException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_CREATE_DIRECTORY_TO_RESTORE_FILE.get(fileToRestore, identifier));
+ }
+ }
+ }
+
+ /**
+ * Restores the file provided by the zip input stream.
+ * <p>
+ * The restore can be virtual: if the outputStream is {@code null}, the file
+ * is not actually restored on disk.
+ */
+ private long restoreFile(ZipInputStream zipInputStream, OutputStream outputStream, RestoreConfig restoreConfig)
+ throws IOException
+ {
+ long totalBytesRead = 0;
+ byte[] buffer = new byte[8192];
+ int bytesRead = zipInputStream.read(buffer);
+ while (bytesRead > 0 && !restoreConfig.isCancelled())
+ {
+ totalBytesRead += bytesRead;
+
+ cryptoEngine.updateHashWith(buffer, 0, bytesRead);
+
+ if (outputStream != null)
+ {
+ outputStream.write(buffer, 0, bytesRead);
+ }
+
+ bytesRead = zipInputStream.read(buffer);
+ }
+ return totalBytesRead;
+ }
+
+ private InputStream openStream() throws DirectoryException
+ {
+ try
+ {
+ return new FileInputStream(archiveFile);
+ }
+ catch (FileNotFoundException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_RESTORE.get(identifier, stackTraceToSingleLineString(e)), e);
+ }
+ }
+
+ private ZipInputStream openZipStream() throws DirectoryException
+ {
+ InputStream inputStream = openStream();
+ inputStream = cryptoEngine.encryptInput(inputStream);
+ return new ZipInputStream(inputStream);
+ }
+
+ private List<String> readAllLines(ZipInputStream zipStream) throws IOException
+ {
+ final ArrayList<String> results = new ArrayList<String>();
+ String line;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(zipStream));
+ while ((line = reader.readLine()) != null)
+ {
+ results.add(line);
+ }
+ return results;
+ }
+
+ }
+
+ /**
+ * Creates a backup of the provided backupable entity.
+ * <p>
+ * The backup is stored in a single zip file in the backup directory.
+ * <p>
+ * If the backup is incremental, then the first entry in the zip is a text
+ * file containing a list of all the log files that are unchanged since the
+ * previous backup. The remaining zip entries are the log files themselves,
+ * which, for an incremental, only include those files that have changed.
+ *
+ * @param backupable
+ * The underlying entity (storage, backend) to be backed up.
+ * @param backupConfig
+ * The configuration to use when performing the backup.
+ * @throws DirectoryException
+ * If a Directory Server error occurs.
+ */
+ public void createBackup(final Backupable backupable, final BackupConfig backupConfig) throws DirectoryException
+ {
+ final NewBackupParams backupParams = new NewBackupParams(backupConfig);
+ final CryptoEngine cryptoEngine = CryptoEngine.forCreation(backupConfig, backupParams);
+ final NewBackupArchive newArchive = new NewBackupArchive(backendID, backupParams, cryptoEngine);
+
+ BackupArchiveWriter archiveWriter = null;
+ try
+ {
+ final ListIterator<Path> files = backupable.getFilesToBackup();
+ final Path rootDirectory = backupable.getDirectory().toPath();
+ archiveWriter = new BackupArchiveWriter(newArchive);
+
+ if (files.hasNext())
+ {
+ if (backupParams.isIncremental) {
+ archiveWriter.writeUnchangedFiles(rootDirectory, files, backupConfig);
+ }
+ archiveWriter.writeChangedFiles(rootDirectory, files, backupConfig);
+ }
+ else {
+ archiveWriter.writeEmptyPlaceHolder();
+ }
+ }
+ finally
+ {
+ closeArchiveWriter(archiveWriter, newArchive.getArchiveFilename(), backupParams.backupDir.getPath());
+ }
+
+ newArchive.updateBackupDirectory();
+
+ if (backupConfig.isCancelled())
+ {
+ // Remove the backup since it may be incomplete
+ removeBackup(backupParams.backupDir, backupParams.backupID);
+ }
+ }
+
+ /**
+ * Restores a backupable entity from its backup, or verify the backup.
+ *
+ * @param backupable
+ * The underlying entity (storage, backend) to be backed up.
+ * @param restoreConfig
+ * The configuration to use when performing the restore.
+ * @throws DirectoryException
+ * If a Directory Server error occurs.
+ */
+ public void restoreBackup(Backupable backupable, RestoreConfig restoreConfig) throws DirectoryException
+ {
+ Path saveDirectory = null;
+ if (!restoreConfig.verifyOnly())
+ {
+ saveDirectory = backupable.beforeRestore();
+ }
+
+ final String backupID = restoreConfig.getBackupID();
+ final ExistingBackupArchive existingArchive =
+ new ExistingBackupArchive(backupID, restoreConfig.getBackupDirectory());
+ final Path restoreDirectory = getRestoreDirectory(backupable, backupID);
+
+ if (existingArchive.hasDependencies())
+ {
+ final BackupArchiveReader zipArchiveReader = new BackupArchiveReader(backupID, existingArchive);
+ final Set<String> unchangedFilesToRestore = zipArchiveReader.readUnchangedDependentFiles();
+ final List<BackupInfo> dependencies = existingArchive.getBackupDependencies();
+ for (BackupInfo dependencyBackupInfo : dependencies)
+ {
+ restoreArchive(restoreDirectory, unchangedFilesToRestore, restoreConfig, backupable, dependencyBackupInfo);
+ }
+ }
+
+ // Restore the final archive file.
+ Set<String> filesToRestore = emptySet();
+ restoreArchive(restoreDirectory, filesToRestore, restoreConfig, backupable, existingArchive.getBackupInfo());
+
+ if (!restoreConfig.verifyOnly())
+ {
+ backupable.afterRestore(restoreDirectory, saveDirectory);
+ }
+ }
+
+ /**
+ * Removes the specified backup if it is possible to do so.
+ *
+ * @param backupDir The backup directory structure with which the
+ * specified backup is associated.
+ * @param backupID The backup ID for the backup to be removed.
+ *
+ * @throws DirectoryException If it is not possible to remove the specified
+ * backup for some reason (e.g., no such backup
+ * exists or there are other backups that are
+ * dependent upon it).
+ */
+ public void removeBackup(BackupDirectory backupDir, String backupID) throws DirectoryException
+ {
+ ExistingBackupArchive archive = new ExistingBackupArchive(backupID, backupDir);
+ archive.removeArchive();
+ }
+
+ private Path getRestoreDirectory(Backupable backupable, String backupID)
+ {
+ File restoreDirectory = backupable.getDirectory();
+ if (!backupable.isDirectRestore())
+ {
+ restoreDirectory = new File(restoreDirectory.getAbsoluteFile() + "-restore-" + backupID);
+ }
+ return restoreDirectory.toPath();
+ }
+
+ private void closeArchiveWriter(BackupArchiveWriter archiveWriter, String backupFile, String backupPath)
+ throws DirectoryException
+ {
+ if (archiveWriter != null)
+ {
+ try
+ {
+ archiveWriter.close();
+ }
+ catch (Exception e)
+ {
+ logger.traceException(e);
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_CLOSE_ZIP_STREAM.get(backupFile, backupPath, stackTraceToSingleLineString(e)), e);
+ }
+ }
+ }
+
+ /**
+ * Restores the content of an archive file.
+ * <p>
+ * If set of files is not empty, only the specified files are restored.
+ * If set of files is empty, all files are restored.
+ *
+ * If the archive is being restored as a dependency, then only files in the
+ * specified set are restored, and the restored files are removed from the
+ * set. Otherwise all files from the archive are restored, and files that are
+ * to be found in dependencies are added to the set.
+ * @param restoreDir
+ * The directory in which files are to be restored.
+ * @param filesToRestore
+ * The set of files to restore. If empty, then all files are
+ * restored.
+ * @param restoreConfig
+ * The restore configuration.
+ * @param backupInfo
+ * The backup containing the files to be restored.
+ *
+ * @throws DirectoryException
+ * If a Directory Server error occurs.
+ * @throws IOException
+ * If an I/O exception occurs during the restore.
+ */
+ private void restoreArchive(Path restoreDir,
+ Set<String> filesToRestore,
+ RestoreConfig restoreConfig,
+ Backupable backupable,
+ BackupInfo backupInfo) throws DirectoryException
+ {
+ String backupID = backupInfo.getBackupID();
+ String backupDirectoryPath = restoreConfig.getBackupDirectory().getPath();
+
+ BackupArchiveReader zipArchiveReader = new BackupArchiveReader(backupID, backupInfo, backupDirectoryPath);
+ zipArchiveReader.restoreArchive(restoreDir, filesToRestore, restoreConfig, backupable);
+ }
+
+ /** Retrieves the full path of the archive file. */
+ private static File retrieveArchiveFile(BackupInfo backupInfo, String backupDirectoryPath)
+ {
+ Map<String,String> backupProperties = backupInfo.getBackupProperties();
+ String archiveFilename = backupProperties.get(BACKUP_PROPERTY_ARCHIVE_FILENAME);
+ return new File(backupDirectoryPath, archiveFilename);
+ }
+
+ /**
+ * Get the information for a given backup ID from the backup directory.
+ *
+ * @param backupDir The backup directory.
+ * @param backupID The backup ID.
+ * @return The backup information, never null.
+ * @throws DirectoryException If the backup information cannot be found.
+ */
+ private static BackupInfo getBackupInfo(BackupDirectory backupDir, String backupID) throws DirectoryException
+ {
+ BackupInfo backupInfo = backupDir.getBackupInfo(backupID);
+ if (backupInfo == null)
+ {
+ LocalizableMessage message = ERR_BACKUP_MISSING_BACKUPID.get(backupID, backupDir.getPath());
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
+ }
+ return backupInfo;
+ }
+
+ /**
+ * Helper method to build a list of files to backup, in the simple case where all files are located
+ * under the provided directory.
+ *
+ * @param directory
+ * The directory containing files to backup.
+ * @param filter
+ * The filter to select files to backup.
+ * @param identifier
+ * Identifier of the backed-up entity
+ * @return the files to backup, which may be empty but never {@code null}
+ * @throws DirectoryException
+ * if an error occurs.
+ */
+ public static List<Path> getFiles(File directory, FileFilter filter, String identifier)
+ throws DirectoryException
+ {
+ File[] files = null;
+ try
+ {
+ files = directory.listFiles(filter);
+ }
+ catch (Exception e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_LIST_LOG_FILES.get(directory.getAbsolutePath(), identifier), e);
+ }
+ if (files == null)
+ {
+ throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
+ ERR_BACKUP_CANNOT_LIST_LOG_FILES.get(directory.getAbsolutePath(), identifier));
+ }
+
+ List<Path> paths = new ArrayList<>();
+ for (File file : files)
+ {
+ paths.add(file.toPath());
+ }
+ return paths;
+ }
+
+ /**
+ * Helper method to save all current files of the provided backupable entity, using
+ * default behavior.
+ *
+ * @param backupable
+ * The entity to backup.
+ * @param identifier
+ * Identifier of the backup
+ * @return the directory where all files are saved.
+ * @throws DirectoryException
+ * If a problem occurs.
+ */
+ public static Path saveCurrentFilesToDirectory(Backupable backupable, String identifier) throws DirectoryException
+ {
+ ListIterator<Path> filesToBackup = backupable.getFilesToBackup();
+ File rootDirectory = backupable.getDirectory();
+ String saveDirectory = rootDirectory.getAbsolutePath() + ".save";
+ BackupManager.saveFilesToDirectory(rootDirectory.toPath(), filesToBackup, saveDirectory, identifier);
+ return Paths.get(saveDirectory);
+ }
+
+ /**
+ * Helper method to move all provided files in a target directory created from
+ * provided target base path, keeping relative path information relative to
+ * root directory.
+ *
+ * @param rootDirectory
+ * A directory which is an ancestor of all provided files.
+ * @param files
+ * The files to move.
+ * @param targetBasePath
+ * Base path of the target directory. Actual directory is built by
+ * adding ".save" and a number, always ensuring that the directory is new.
+ * @param identifier
+ * Identifier of the backup
+ * @return the actual directory where all files are saved.
+ * @throws DirectoryException
+ * If a problem occurs.
+ */
+ public static Path saveFilesToDirectory(Path rootDirectory, ListIterator<Path> files, String targetBasePath,
+ String identifier) throws DirectoryException
+ {
+ Path targetDirectory = null;
+ try
+ {
+ targetDirectory = createDirectoryWithNumericSuffix(targetBasePath, identifier);
+ while (files.hasNext())
+ {
+ Path file = files.next();
+ Path relativeFilePath = rootDirectory.relativize(file);
+ Path targetFile = targetDirectory.resolve(relativeFilePath);
+ Files.createDirectories(targetFile.getParent());
+ Files.move(file, targetFile);
+ }
+ return targetDirectory;
+ }
+ catch (IOException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_SAVE_FILES_BEFORE_RESTORE.get(rootDirectory, targetDirectory, identifier,
+ stackTraceToSingleLineString(e)), e);
+ }
+ }
+
+ /**
+ * Creates a new directory based on the provided directory path, by adding a
+ * suffix number that is guaranteed to be the highest.
+ */
+ static Path createDirectoryWithNumericSuffix(final String baseDirectoryPath, String identifier)
+ throws DirectoryException
+ {
+ try
+ {
+ int number = getHighestSuffixNumberForPath(baseDirectoryPath);
+ String path = baseDirectoryPath + (number + 1);
+ Path directory = Paths.get(path);
+ Files.createDirectories(directory);
+ return directory;
+ }
+ catch (IOException e)
+ {
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ ERR_BACKUP_CANNOT_CREATE_SAVE_DIRECTORY.get(baseDirectoryPath, identifier,
+ stackTraceToSingleLineString(e)), e);
+ }
+ }
+
+ /**
+ * Returns a number that correspond to the highest suffix number existing for the provided base path.
+ * <p>
+ * Example: given the following directory structure
+ * <pre>
+ * +--- someDir
+ * | \--- directory
+ * | \--- directory1
+ * | \--- directory2
+ * | \--- directory10
+ * </pre>
+ * getHighestSuffixNumberForPath("directory") returns 10.
+ *
+ * @param basePath
+ * A base path to a file or directory, without any suffix number.
+ * @return the highest suffix number, or 0 if no suffix number exists
+ * @throws IOException
+ * if an error occurs.
+ */
+ private static int getHighestSuffixNumberForPath(final String basePath) throws IOException
+ {
+ final File baseFile = new File(basePath);
+ final File[] existingFiles = baseFile.getParentFile().listFiles();
+ final Pattern pattern = Pattern.compile(basePath + "\\d*");
+ int highestNumber = 0;
+ for (File file : existingFiles)
+ {
+ final String name = file.getCanonicalPath();
+ if (pattern.matcher(name).matches())
+ {
+ String numberAsString = name.substring(basePath.length());
+ int number = numberAsString.isEmpty() ? 0 : Integer.valueOf(numberAsString);
+ highestNumber = number > highestNumber ? number : highestNumber;
+ }
+ }
+ return highestNumber;
+ }
+
+}
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/backend.properties b/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
index 3c4f0fb..5542b86 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/backend.properties
@@ -978,9 +978,6 @@
restored from the archive in directory %s
ERR_BACKUP_MISSING_BACKUPID_407=The information for backup %s could \
not be found in the backup directory %s
-ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR_408=An error occurred \
- while attempting to update the backup descriptor file %s with information \
- about the backup: %s
ERR_SCHEMA_MODIFY_RULEID_CONFLICTS_FOR_ADD_DSR_409=Unable to add DIT \
structure rule %s because its rule identifier conflicts with existing DIT structure \
rule (%s)
@@ -1034,4 +1031,10 @@
space is restored
WARN_DISK_SPACE_FULL_THRESHOLD_CROSSED_432=Disk free space of %d bytes for directory %s is now below disk low \
threshold of %d bytes. Backend %s is now offline and will no longer accept any operations until sufficient \
- disk space is restored
\ No newline at end of file
+ disk space is restored
+ERR_BACKEND_LIST_FILES_TO_BACKUP_433=An error occurred while trying to \
+ list the files to backup for backend '%s': %s
+ERR_BACKEND_SWITCH_TO_APPEND_MODE_434=An error occurred while trying to \
+ switch to append mode for backend '%s': %s
+ERR_BACKEND_END_APPEND_MODE_435=An error occurred while trying to \
+ end append mode for backend '%s': %s
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/jeb.properties b/opendj-server-legacy/src/messages/org/opends/messages/jeb.properties
index 7850b7e..17ee5fa 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/jeb.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/jeb.properties
@@ -112,48 +112,10 @@
pre-loading
ERR_JEB_CACHE_PRELOAD_62=An error occurred while preloading the \
database cache for backend %s: %s
-ERR_JEB_BACKUP_CANNOT_GET_MAC_63=An error occurred while attempting to \
- obtain the %s MAC provider to create the signed hash for the backup: %s
-ERR_JEB_BACKUP_CANNOT_GET_DIGEST_64=An error occurred while attempting \
- to obtain the %s message digest to create the hash for the backup: %s
-ERR_JEB_BACKUP_CANNOT_CREATE_ARCHIVE_FILE_65=An error occurred while \
- trying to create the database archive file %s in directory %s: %s
-ERR_JEB_BACKUP_CANNOT_GET_CIPHER_66=An error occurred while attempting \
- to obtain the cipher to use to encrypt the backup: %s
-ERR_JEB_BACKUP_ZIP_COMMENT_67=%s backup %s of backend %s
-ERR_JEB_BACKUP_CANNOT_LIST_LOG_FILES_68=An error occurred while \
- attempting to obtain a list of the files in directory %s to include in the \
- database backup: %s
-ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE_69=An error occurred while \
- attempting to back up database file %s: %s
-ERR_JEB_BACKUP_CANNOT_CLOSE_ZIP_STREAM_70=An error occurred while \
- trying to close the database archive file %s in directory %s: %s
-ERR_JEB_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR_71=An error occurred \
- while attempting to update the backup descriptor file %s with information \
- about the database backup: %s
-ERR_JEB_BACKUP_UNSIGNED_HASH_ERROR_72=The computed hash of backup %s \
- is different to the value computed at time of backup
-ERR_JEB_BACKUP_SIGNED_HASH_ERROR_73=The computed signed hash of backup \
- %s is different to the value computed at time of backup
-ERR_JEB_INCR_BACKUP_REQUIRES_FULL_74=A full backup must be taken \
- before an incremental backup can be taken
-ERR_JEB_CANNOT_RENAME_RESTORE_DIRECTORY_75=The directory %s, \
- containing the files restored from backup, could not be renamed to the \
- backend directory %s
-ERR_JEB_INCR_BACKUP_FROM_WRONG_BASE_76=One of the following base \
- backup IDs must be specified for the incremental backup: %s
ERR_JEB_CANNOT_CREATE_BACKUP_TAG_FILE_77=The backup tag file %s could \
not be created in %s
-ERR_JEB_BACKUP_CANNOT_RESTORE_78=An error occurred while attempting to \
- restore the files from backup %s: %s
-ERR_JEB_BACKUP_MISSING_BACKUPID_79=The information for backup %s could \
- not be found in the backup directory %s
-NOTE_JEB_BACKUP_FILE_UNCHANGED_82=Not changed: %s
NOTE_JEB_BACKUP_CLEANER_ACTIVITY_83=Including %s additional log file(s) due \
to cleaner activity
-NOTE_JEB_BACKUP_VERIFY_FILE_84=Verifying: %s
-NOTE_JEB_BACKUP_RESTORED_FILE_85=Restored: %s (size %d)
-NOTE_JEB_BACKUP_ARCHIVED_FILE_86=Archived: %s
NOTE_JEB_EXPORT_FINAL_STATUS_87=Exported %d entries and skipped %d in %d \
seconds (average rate %.1f/sec)
NOTE_JEB_EXPORT_PROGRESS_REPORT_88=Exported %d records and skipped %d (recent \
diff --git a/opendj-server-legacy/src/messages/org/opends/messages/utility.properties b/opendj-server-legacy/src/messages/org/opends/messages/utility.properties
index b814f59..38a158b 100644
--- a/opendj-server-legacy/src/messages/org/opends/messages/utility.properties
+++ b/opendj-server-legacy/src/messages/org/opends/messages/utility.properties
@@ -572,4 +572,48 @@
ERR_LDAP_CONN_BAD_INTEGER_302=Invalid integer number "%s". Please \
enter a valid integer
ERR_ARG_SUBCOMMAND_INVALID_303=Invalid subcommand
-WARN_UNABLE_TO_USE_FILESYSTEM_API_304=Unable to gather information on Filesystem APIs, disk monitoring will be verbose
\ No newline at end of file
+WARN_UNABLE_TO_USE_FILESYSTEM_API_304=Unable to gather information on Filesystem APIs, disk monitoring will be verbose
+ERR_BACKUP_CANNOT_GET_MAC_305=An error occurred while attempting to \
+ obtain the %s MAC provider to create the signed hash for the backup: %s
+ERR_BACKUP_CANNOT_GET_DIGEST_306=An error occurred while attempting \
+ to obtain the %s message digest to create the hash for the backup: %s
+ERR_BACKUP_CANNOT_CREATE_ARCHIVE_FILE_307=An error occurred while \
+ trying to create the archive file %s in directory %s for the backup %s: %s
+ERR_BACKUP_CANNOT_GET_CIPHER_308=An error occurred while attempting \
+ to obtain the cipher to use to encrypt the backup: %s
+ERR_BACKUP_ZIP_COMMENT_309=%s backup %s
+ERR_BACKUP_CANNOT_LIST_LOG_FILES_310=An error occurred while \
+ attempting to obtain a list of the files in directory %s to include in the \
+ backup: %s
+ERR_BACKUP_CANNOT_WRITE_ARCHIVE_FILE_311=An error occurred while \
+ attempting to back up file %s of backup %s: %s
+ERR_BACKUP_CANNOT_CLOSE_ZIP_STREAM_312=An error occurred while \
+ trying to close the archive file %s in directory %s: %s
+ERR_BACKUP_UNSIGNED_HASH_ERROR_313=The computed hash of backup %s \
+ is different to the value computed at time of backup
+ERR_BACKUP_SIGNED_HASH_ERROR_314=The computed signed hash of backup \
+ %s is different to the value computed at time of backup
+ERR_CANNOT_RENAME_RESTORE_DIRECTORY_315=The directory %s, \
+ containing the files restored from backup, could not be renamed to the \
+ directory %s
+ERR_BACKUP_CANNOT_UPDATE_BACKUP_DESCRIPTOR_316=An error occurred \
+ while attempting to update the backup descriptor file %s with information \
+ about the backup: %s
+ERR_BACKUP_CANNOT_RESTORE_317=An error occurred while attempting to \
+ restore the files from backup %s: %s
+NOTE_BACKUP_FILE_UNCHANGED_318=Backup file has not changed: %s
+NOTE_BACKUP_VERIFY_FILE_319=Verifying backup file: %s
+NOTE_BACKUP_RESTORED_FILE_320=Restored backup file: %s (size %d)
+NOTE_BACKUP_ARCHIVED_FILE_321=Archived backup file: %s
+WARN_BACKUPDB_INCREMENTAL_NOT_FOUND_DOING_NORMAL_322=Could not find any \
+ backup in '%s'. A full backup will be executed
+ERR_BACKUP_CANNOT_GET_MAC_KEY_ID_323=An error occurred while attempting to \
+ obtain the MAC key ID to create the signed hash for the backup %s : %s
+ERR_BACKUP_CANNOT_CREATE_DIRECTORY_TO_RESTORE_FILE_324=An error occurred while \
+ attempting to create a directory to restore the file %s for backup of %s
+ERR_BACKUP_CANNOT_SAVE_FILES_BEFORE_RESTORE_325=An error occurred while \
+ attempting to save files from root directory %s to target directory %s, for \
+ backup of %s : %s
+ERR_BACKUP_CANNOT_CREATE_SAVE_DIRECTORY_326=An error occurred while \
+ attempting to create a save directory with base path %s before restore of \
+ backup of %s: %s
\ No newline at end of file
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
index 6f41e8d..c06cef4 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -26,6 +26,10 @@
*/
package org.opends.server.backends.jeb;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -43,6 +47,7 @@
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.LocalDBBackendCfgDefn;
import org.opends.server.admin.std.server.LocalDBBackendCfg;
+import org.opends.server.backends.jeb.BackendImpl.JELogFilesIterator;
import org.opends.server.controls.SubtreeDeleteControl;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
@@ -63,6 +68,7 @@
import org.opends.server.types.RDN;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.util.Base64;
+import org.opends.server.util.StaticUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
@@ -81,6 +87,7 @@
import static org.opends.server.protocols.internal.InternalClientConnection.*;
import static org.opends.server.protocols.internal.Requests.*;
import static org.opends.server.types.Attributes.*;
+import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
/**
@@ -1406,4 +1413,111 @@
}
}
+ @Test
+ public void testJELogFilesIterator() throws Exception
+ {
+ File rootDir = TestCaseUtils.createTemporaryDirectory("jeLogFilesIterator");
+ try
+ {
+ createLogFilesInDirectory(rootDir.toPath(), "content", 0, 3);
+ JELogFilesIterator iterator = new BackendImpl.JELogFilesIterator(rootDir, "backendID");
+
+ assertLogFilesIterator(iterator, rootDir, 0, 2);
+ assertThat(iterator.hasNext()).isFalse();
+ }
+ finally
+ {
+ StaticUtils.recursiveDelete(rootDir);
+ }
+ }
+
+ @Test
+ public void testJELogFilesIteratorWhenFileIsDeletedAndNewOneAdded() throws Exception
+ {
+ File rootDir = TestCaseUtils.createTemporaryDirectory("jeLogFilesIteratorDelete");
+ try
+ {
+ createLogFilesInDirectory(rootDir.toPath(), "content", 0, 2);
+ JELogFilesIterator iterator = new BackendImpl.JELogFilesIterator(rootDir, "backendID");
+
+ assertLogFilesIterator(iterator, rootDir, 0, 1);
+ assertThat(iterator.hasNext()).isFalse();
+
+ // delete first file log0.jdb and create a new one log2.jdb
+ new File(rootDir, "log0.jdb").delete();
+ createLogFilesInDirectory(rootDir.toPath(), "content", 2, 1);
+
+ assertLogFilesIterator(iterator, rootDir, 2, 2);
+ assertThat(iterator.hasNext()).isFalse();
+ }
+ finally
+ {
+ StaticUtils.recursiveDelete(rootDir);
+ }
+ }
+
+ @Test
+ public void testJELogFilesIteratorWhenFileIsDeletedAndLastOneHasLargerSize() throws Exception
+ {
+ File rootDir = TestCaseUtils.createTemporaryDirectory("jeLogFilesIteratorDelete2");
+ try
+ {
+ createLogFilesInDirectory(rootDir.toPath(), "content", 0, 2);
+ JELogFilesIterator iterator = new BackendImpl.JELogFilesIterator(rootDir, "backendID");
+
+ assertLogFilesIterator(iterator, rootDir, 0, 1);
+
+ // delete first file log0.jdb and update last one with larger content
+ new File(rootDir, "log0.jdb").delete();
+ new File(rootDir, "log1.jdb").delete();
+ createLogFilesInDirectory(rootDir.toPath(), "morecontent", 1, 1);
+
+ assertLogFilesIterator(iterator, rootDir, 1, 1);
+ assertThat(iterator.hasNext()).isFalse();
+ }
+ finally
+ {
+ StaticUtils.recursiveDelete(rootDir);
+ }
+ }
+
+ private void assertLogFilesIterator(JELogFilesIterator iterator, File rootDir, int from, int to)
+ {
+ for (int i = from; i <= to; i++)
+ {
+ assertThat(iterator.hasNext()).as("hasNext expected to be true for " + i).isTrue();
+ assertThat(iterator.next().toFile()).isEqualTo(new File(rootDir, "log" + i + ".jdb"));
+ }
+ }
+
+ /**
+ * Creates dummy "logN.jdb" (where N is a number) files in given directory
+ * with provided label as content for files.
+ */
+ private List<Path> createLogFilesInDirectory(Path directory, String label, int start, int numberOfFiles)
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ for (int i = start; i < start+numberOfFiles; i++)
+ {
+ String filename = "log" + i + ".jdb";
+ Path file = directory.resolve(filename);
+ createFile(file, StaticUtils.getBytes(label));
+ files.add(file);
+ }
+ return files;
+ }
+
+ private void createFile(Path file, byte[] content) throws Exception {
+ OutputStream output = new FileOutputStream(file.toFile(), false);
+ try
+ {
+ output.write(content);
+ }
+ finally {
+ close(output);
+ }
+ }
+
+
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java
index bc67726..42399a7 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/tasks/TestBackupAndRestore.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions Copyright 2014 ForgeRock AS.
+ * Portions Copyright 2014-2015 ForgeRock AS.
*/
package org.opends.server.tasks;
@@ -128,6 +128,12 @@
TaskState.COMPLETED_SUCCESSFULLY
},
{
+ // Restore a SchemaBackend
+ TestCaseUtils.makeEntry(restoreTask(
+ "ds-backup-directory-path: bak" + File.separator + "schema")),
+ TaskState.COMPLETED_SUCCESSFULLY
+ },
+ {
// Non-existent restore directory-path.
TestCaseUtils.makeEntry(restoreTask(
"ds-backup-directory-path: missing"
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java
new file mode 100644
index 0000000..af00d86
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/util/BackupManagerTestCase.java
@@ -0,0 +1,376 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2015 ForgeRock AS
+ */
+package org.opends.server.util;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.api.Backupable;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DN;
+import org.opends.server.types.RestoreConfig;
+
+import org.opends.server.util.StaticUtils;
+import org.testng.Reporter;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+@Test(groups = { "precommit" }, sequential = true)
+public class BackupManagerTestCase extends DirectoryServerTestCase
+{
+
+ private static final String ENTRY_DN = "dc=example,dc=com";
+
+ private static final String FILE_NAME_PREFIX = "file_";
+ private static final String BACKEND_ID = "backendID";
+ private static final String BACKUP_ID = "backupID";
+
+ @BeforeClass
+ public void setUp() throws Exception
+ {
+ // Need the schema to be available, so make sure the server is started.
+ // startFakeServer() is insufficient because we also need the CryptoManager to be initialized
+ TestCaseUtils.startServer();
+ }
+
+ @DataProvider
+ Object[][] backupData() throws Exception {
+
+ // For each case is provided
+ // - a label identifying the case (not used in method but allow to identify easily the case in IDE)
+ // - a mock of a backupable (building the mock also involves creating directory and files to backup)
+ // - a backup config
+ // - a restore config
+
+ String label0 = "nohash";
+ Backupable backupable0 = buildBackupable(createSourceDirectory(label0), 3);
+ BackupDirectory backupDir0 = buildBackupDir(label0);
+ BackupConfig backupConfig0 = new BackupConfig(backupDir0, BACKUP_ID, false);
+ RestoreConfig restoreConfig0 = new RestoreConfig(backupDir0, BACKUP_ID, false);
+
+ String label1 = "unsignedhash";
+ Backupable backupable1 = buildBackupable(createSourceDirectory(label1), 3);
+ BackupDirectory backupDir1 = buildBackupDir(label1);
+ BackupConfig backupConfig1 = new BackupConfig(backupDir1, BACKUP_ID, false);
+ backupConfig1.setHashData(true);
+ RestoreConfig restoreConfig1 = new RestoreConfig(backupDir1, BACKUP_ID, false);
+
+ String label2 = "signedhash";
+ Backupable backupable2 = buildBackupable(createSourceDirectory(label2), 3);
+ BackupDirectory backupDir2 = buildBackupDir(label2);
+ BackupConfig backupConfig2 = new BackupConfig(backupDir2, BACKUP_ID, false);
+ backupConfig2.setHashData(true);
+ backupConfig2.setSignHash(true);
+ RestoreConfig restoreConfig2 = new RestoreConfig(backupDir2, BACKUP_ID, false);
+
+ String label3 = "encrypted_compressed";
+ Backupable backupable3 = buildBackupable(createSourceDirectory(label3), 3);
+ BackupDirectory backupDir3 = buildBackupDir(label3);
+ BackupConfig backupConfig3 = new BackupConfig(backupDir3, BACKUP_ID, false);
+ backupConfig3.setEncryptData(true);
+ backupConfig3.setCompressData(true);
+ RestoreConfig restoreConfig3 = new RestoreConfig(backupDir3, BACKUP_ID, false);
+
+ // should perform a normal backup in absence of incremental base ID
+ String label4 = "incremental_without_incrementalBaseID";
+ Backupable backupable4 = buildBackupable(createSourceDirectory(label4), 3);
+ BackupDirectory backupDir4 = buildBackupDir(label4);
+ BackupConfig backupConfig4 = new BackupConfig(backupDir4, BACKUP_ID, true);
+ backupConfig4.setHashData(true);
+ RestoreConfig restoreConfig4 = new RestoreConfig(backupDir4, BACKUP_ID, false);
+
+ String label5 = "noFiles";
+ Backupable backupable5 = buildBackupable(createSourceDirectory(label5), 0);
+ BackupDirectory backupDir5 = buildBackupDir(label5);
+ BackupConfig backupConfig5 = new BackupConfig(backupDir5, BACKUP_ID, false);
+ RestoreConfig restoreConfig5 = new RestoreConfig(backupDir5, BACKUP_ID, false);
+
+ String label6 = "multiple_directories";
+ Backupable backupable6 = buildBackupableForMultipleDirectoriesCase(createSourceDirectory(label6), 3);
+ BackupDirectory backupDir6 = buildBackupDir(label6);
+ BackupConfig backupConfig6 = new BackupConfig(backupDir6, BACKUP_ID, false);
+ RestoreConfig restoreConfig6 = new RestoreConfig(backupDir6, BACKUP_ID, false);
+
+ return new Object[][] {
+ { label0, backupable0, backupConfig0, restoreConfig0 },
+ { label1, backupable1, backupConfig1, restoreConfig1 },
+ { label2, backupable2, backupConfig2, restoreConfig2 },
+ { label3, backupable3, backupConfig3, restoreConfig3 },
+ { label4, backupable4, backupConfig4, restoreConfig4 },
+ { label5, backupable5, backupConfig5, restoreConfig5 },
+ { label6, backupable6, backupConfig6, restoreConfig6 },
+ };
+ }
+
+ /**
+ * This test encompasses creation, restore and remove of a backup.
+ *
+ * It allows to ensure that a backup can actually be restored.
+ */
+ @Test(dataProvider="backupData")
+ public void testCreateBackupThenRestoreThenRemove(String label, Backupable backupable, BackupConfig backupConfig,
+ RestoreConfig restoreConfig) throws Exception
+ {
+ BackupManager backupManager = new BackupManager(BACKEND_ID);
+
+ // create and check archive files
+ backupManager.createBackup(backupable, backupConfig);
+
+ String backupPath = backupConfig.getBackupDirectory().getPath();
+ assertThat(new File(backupPath, getArchiveFileName(BACKUP_ID))).exists();
+ assertThat(new File(backupPath, "backup.info")).exists();
+
+ // change content of directory to later check that backup is recovering everything
+ removeBackedUpFiles(backupable);
+
+ // restore and check list of files
+ backupManager.restoreBackup(backupable, restoreConfig);
+
+ assertAllFilesAreRestoredCorrectly(backupable);
+
+ // remove the backup archive and check
+ backupManager.removeBackup(backupConfig.getBackupDirectory(), BACKUP_ID);
+ assertThat(new File(backupPath, getArchiveFileName(BACKUP_ID))).doesNotExist();
+
+ //cleanDirectories(sourceDirectory, backupPath);
+ }
+
+ /**
+ * This test encompasses creation, restore and remove of an incremental backup.
+ *
+ * It allows to ensure that a backup can actually be restored.
+ */
+ @Test()
+ public void testCreateIncrementalBackupThenRestoreThenRemove() throws Exception
+ {
+ Path sourceDirectory = createSourceDirectory("incremental");
+ BackupDirectory backupDir = buildBackupDir("incremental");
+ BackupManager backupManager = new BackupManager(BACKEND_ID);
+
+ // perform first backup with 2 files
+ Backupable backupable0 = buildBackupable(sourceDirectory, 2);
+ String initialBackupId = BACKUP_ID + "_0";
+ BackupConfig backupConfig0 = new BackupConfig(backupDir, initialBackupId, true);
+
+ backupManager.createBackup(backupable0, backupConfig0);
+
+ // check archive and info file
+ String backupPath = backupDir.getPath();
+ assertThat(new File(backupPath, getArchiveFileName(initialBackupId))).exists();
+ assertThat(new File(backupPath, "backup.info")).exists();
+
+ // perform second backup with 4 files (2 initial files plus 2 new files)
+ // now backup with id "backupID" should depend on backup with id "backupID_0"
+ Backupable backupable1 = buildBackupable(sourceDirectory, 4);
+ BackupConfig backupConfig1 = new BackupConfig(backupDir, BACKUP_ID, true);
+
+ backupManager.createBackup(backupable1, backupConfig1);
+
+ assertThat(new File(backupPath, getArchiveFileName(initialBackupId))).exists();
+ assertThat(new File(backupPath, getArchiveFileName(BACKUP_ID))).exists();
+ assertThat(new File(backupPath, "backup.info")).exists();
+ assertThat(new File(backupPath, "backup.info.save")).exists();
+
+ // change content of directory to later check that backup is recovering everything
+ removeBackedUpFiles(backupable1);
+
+ // restore and check list of files
+ RestoreConfig restoreConfig = new RestoreConfig(backupDir, BACKUP_ID, false);
+ backupManager.restoreBackup(backupable1, restoreConfig);
+
+ assertAllFilesAreRestoredCorrectly(backupable1);
+
+ // remove the backup archive and check
+ backupManager.removeBackup(backupDir, BACKUP_ID);
+ assertThat(new File(backupPath, getArchiveFileName(BACKUP_ID))).doesNotExist();
+ backupManager.removeBackup(backupDir, initialBackupId);
+ assertThat(new File(backupPath, getArchiveFileName(initialBackupId))).doesNotExist();
+
+ cleanDirectories(sourceDirectory, backupPath);
+ }
+
+ @Test
+ public void testCreateDirectoryWithNumericSuffix() throws Exception
+ {
+ File directory = TestCaseUtils.createTemporaryDirectory("createDirectory-");
+ String dirPath = directory.getAbsolutePath();
+ // delete the directory to ensure creation works fine when there is no directory
+ directory.delete();
+
+ Path dir = BackupManager.createDirectoryWithNumericSuffix(dirPath, BACKUP_ID);
+ assertThat(dir.toString()).isEqualTo(dirPath + "1");
+
+ Path dir2 = BackupManager.createDirectoryWithNumericSuffix(dirPath, BACKUP_ID);
+ assertThat(dir2.toString()).isEqualTo(dirPath + "2");
+
+ recursiveDelete(dir.toFile());
+ recursiveDelete(dir2.toFile());
+ }
+
+ @Test
+ public void testSaveFilesToDirectory() throws Exception
+ {
+ Backupable backupable = buildBackupableForMultipleDirectoriesCase(createSourceDirectory("saveFiles-root"), 2);
+ File rootDir = backupable.getDirectory();
+ File targetDir = TestCaseUtils.createTemporaryDirectory("saveFiles-target");
+
+ File actualTargetDir = BackupManager.saveFilesToDirectory(rootDir.toPath(), backupable.getFilesToBackup(),
+ targetDir.getCanonicalPath(), BACKUP_ID).toFile();
+
+ // all files should have been saved in the target directory, with correct sub-path
+ assertThat(new File(actualTargetDir, FILE_NAME_PREFIX+0)).exists();
+ assertThat(new File(actualTargetDir, FILE_NAME_PREFIX+1)).exists();
+ File subdir = new File(actualTargetDir, "subdir");
+ assertThat(new File(subdir, FILE_NAME_PREFIX+0)).exists();
+ assertThat(new File(subdir, FILE_NAME_PREFIX+1)).exists();
+
+ recursiveDelete(rootDir);
+ recursiveDelete(targetDir);
+ recursiveDelete(actualTargetDir);
+ }
+
+ private void cleanDirectories(Path sourceDirectory, String backupPath)
+ {
+ StaticUtils.recursiveDelete(sourceDirectory.toFile());
+ StaticUtils.recursiveDelete(new File(backupPath));
+ }
+
+ private String getArchiveFileName(String backupId)
+ {
+ return "backup-" + BACKEND_ID + "-" + backupId;
+ }
+
+ private void assertAllFilesAreRestoredCorrectly(Backupable backupable) throws Exception
+ {
+ ListIterator<Path> files = backupable.getFilesToBackup();
+ while (files.hasNext())
+ {
+ Path file = files.next();
+ assertThat(file.toFile()).exists();
+ assertThat(file.toFile()).hasContent(file.getFileName().toString());
+ }
+ }
+
+ private void removeBackedUpFiles(Backupable backupable) throws Exception
+ {
+ ListIterator<Path> it = backupable.getFilesToBackup();
+ while (it.hasNext())
+ {
+ Path file = it.next();
+ Files.deleteIfExists(file);
+ }
+ }
+
+ private BackupDirectory buildBackupDir(String label) throws Exception
+ {
+ File backupDirectory = TestCaseUtils.createTemporaryDirectory("backupDirectory-" + label + "-");
+ Reporter.log("Create backup directory:" + backupDirectory, true);
+ BackupDirectory backupDir = new BackupDirectory(backupDirectory.getAbsolutePath(), DN.valueOf(ENTRY_DN));
+ return backupDir;
+ }
+
+ private Backupable buildBackupable(Path sourceDirectory, int numberOfFiles) throws Exception
+ {
+ List<Path> files = createFilesInDirectoryToBackup(sourceDirectory, numberOfFiles);
+
+ Backupable backupable = mock(Backupable.class);
+ when(backupable.getDirectory()).thenReturn(sourceDirectory.toFile());
+ when(backupable.getFilesToBackup()).thenReturn(files.listIterator());
+ when(backupable.isDirectRestore()).thenReturn(true);
+
+ return backupable;
+ }
+
+ /**
+ * Create files in source directory + additional files under a subdirectory of source directory
+ */
+ private Backupable buildBackupableForMultipleDirectoriesCase(Path sourceDirectory, int numberOfFiles)
+ throws Exception
+ {
+ List<Path> files = createFilesInDirectoryToBackup(sourceDirectory, numberOfFiles);
+
+ // create an additional subdirectory with files
+ Path subdir = sourceDirectory.resolve("subdir");
+ Files.createDirectory(subdir);
+ List<Path> subdirFiles = createFilesInDirectoryToBackup(subdir, numberOfFiles);
+ files.addAll(subdirFiles);
+
+ Backupable backupable = mock(Backupable.class);
+ when(backupable.getDirectory()).thenReturn(sourceDirectory.toFile());
+ when(backupable.getFilesToBackup()).thenReturn(files.listIterator());
+
+ return backupable;
+ }
+
+ private Path createSourceDirectory(String label) throws IOException
+ {
+ File sourceDirectory = TestCaseUtils.createTemporaryDirectory("dirToBackup-" + label + "-");
+ Reporter.log("Create directory to backup:" + sourceDirectory, true);
+ return sourceDirectory.toPath();
+ }
+
+ private List<Path> createFilesInDirectoryToBackup(Path directory, int numberOfFiles)
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ for (int i = 0; i < numberOfFiles; i++)
+ {
+ String filename = FILE_NAME_PREFIX + i;
+ Path file = directory.resolve(filename);
+ createFile(file, StaticUtils.getBytes(filename));
+ files.add(file);
+ }
+ return files;
+ }
+
+ private void createFile(Path file, byte[] content) throws Exception {
+ OutputStream output = new FileOutputStream(file.toFile(), false);
+ try
+ {
+ output.write(content);
+ }
+ finally {
+ close(output);
+ }
+ }
+
+}
--
Gitblit v1.10.0