| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.qpid.server.store.berkeleydb; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Properties; |
| |
| import com.sleepycat.je.DatabaseException; |
| import com.sleepycat.je.Environment; |
| import com.sleepycat.je.EnvironmentConfig; |
| import com.sleepycat.je.util.DbBackup; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.qpid.server.store.StoreException; |
| import org.apache.qpid.server.util.CommandLineParser; |
| import org.apache.qpid.server.util.FileUtils; |
| |
| /** |
| * BDBBackup is a utility for taking hot backups of the current state of a BDB transaction log database. |
| * <p> |
| * This utility makes the following assumptions/performs the following actions: |
| * <p> |
| * <ul> <li>The from and to directory locations will already exist. This scripts does not create them. <li>If this |
| * script fails to complete in one minute it will terminate. <li>This script always exits with code 1 on error, code 0 |
| * on success (standard unix convention). <li>This script will log out at info level, when it starts and ends and a list |
| * of all files backed up. <li>This script logs all errors at error level. <li>This script does not perform regular |
| * backups, wrap its calling script in a cron job or similar to do this. </ul> |
| * <p> |
| * This utility is build around the BDB provided backup helper utility class, DbBackup. This utility class provides |
| * an ability to force BDB to stop writing to the current log file set, whilst the backup is taken, to ensure that a |
| * consistent snapshot is acquired. Preventing BDB from writing to the current log file set, does not stop BDB from |
| * continuing to run concurrently while the backup is running, it simply moves onto a new set of log files; this |
| * provides a 'hot' backup facility. |
| * <p> |
| * DbBackup can also help with incremental backups, by providing the number of the last log file backed up. |
| * Subsequent backups can be taken, from later log files only. In a messaging application, messages are not expected to |
| * be long-lived in most cases, so the log files will usually have been completely turned over between backups. This |
| * utility does not support incremental backups for this reason. |
| * <p> |
| * If the database is locked by BDB, as is required when using transactions, and therefore will always be the case |
| * in Qpid, this utility cannot make use of the DbBackup utility in a seperate process. DbBackup, needs to ensure that |
| * the BDB envinronment used to take the backup has exclusive write access to the log files. This utility can take a |
| * backup as a standalone utility against log files, when a broker is not running, using the {@link #takeBackup(String, |
| *String,com.sleepycat.je.Environment)} method. |
| * <p> |
| * A separate backup machanism is provided by the {@link #takeBackupNoLock(String,String)} method which can take a |
| * hot backup against a running broker. This works by finding out the set of files to copy, and then opening them all to |
| * read, and repeating this process until a consistent set of open files is obtained. This is done to avoid the |
| * situation where the BDB cleanup thread deletes a file, between the directory listing and opening of the file to copy. |
| * All consistently opened files are copied. This is the default mechanism the the {@link #main} method of this utility |
| * uses. |
| */ |
| public class BDBBackup |
| { |
| /** Used for debugging. */ |
| private static final Logger log = LoggerFactory.getLogger(BDBBackup.class); |
| |
| /** Used for communicating with the user. */ |
| private static final Logger console = LoggerFactory.getLogger("Console"); |
| |
| /** Defines the suffix used to identify BDB log files. */ |
| private static final String LOG_FILE_SUFFIX = ".jdb"; |
| |
| /** Defines the command line format for this utility. */ |
| public static final String[][] COMMAND_LINE_SPEC = |
| new String[][] |
| { |
| { "fromdir", "The path to the directory to back the bdb log file from.", "dir", "true" }, |
| { "todir", "The path to the directory to save the backed up bdb log files to.", "dir", "true" } |
| }; |
| |
| /** Defines the timeout to terminate the backup operation on if it fails to complete. One minute. */ |
| public static final long TIMEOUT = 60000; |
| |
| /** |
| * Runs a backup of the BDB log files in a specified directory, copying the backed up files to another specified |
| * directory. |
| * <p> |
| * The following arguments must be specified: |
| * <table> |
| * <caption>Command Line</caption> <tr><th> Option <th> Comment <tr><td> -fromdir <td> The path to the |
| * directory to back the bdb log file from. <tr><td> -todir <td> The path to the directory to save the backed up |
| * bdb log files to. </table> |
| * |
| * @param args The command line arguments. |
| */ |
| public static void main(String[] args) |
| { |
| // Process the command line using standard handling (errors and usage followed by System.exit when it is wrong). |
| Properties options = |
| CommandLineParser.processCommandLine(args, new CommandLineParser(COMMAND_LINE_SPEC), System.getProperties()); |
| |
| // Extract the from and to directory locations and perform a backup between them. |
| try |
| { |
| String fromDir = options.getProperty("fromdir"); |
| String toDir = options.getProperty("todir"); |
| |
| log.info("BDBBackup Utility: Starting Hot Backup."); |
| |
| BDBBackup bdbBackup = new BDBBackup(); |
| String[] backedUpFiles = bdbBackup.takeBackupNoLock(fromDir, toDir); |
| |
| if (log.isInfoEnabled()) |
| { |
| log.info("BDBBackup Utility: Hot Backup Completed."); |
| log.info(backedUpFiles.length + " file(s) backed-up:"); |
| for(String backedUpFile : backedUpFiles) |
| { |
| log.info(backedUpFile); |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| console.info("Backup script encountered an error and has failed: " + e.getMessage()); |
| log.error("Backup script got exception: " + e.getMessage(), e); |
| System.exit(1); |
| } |
| } |
| |
| /** |
| * Creates a backup of the BDB log files in the source directory, copying them to the destination directory. |
| * |
| * @param fromdir The source directory path. |
| * @param todir The destination directory path. |
| * @param environment An open BDB environment to perform the back up. |
| * |
| * @throws DatabaseException Any underlying execeptions from BDB are allowed to fall through. |
| */ |
| public void takeBackup(String fromdir, String todir, Environment environment) throws DatabaseException |
| { |
| DbBackup backupHelper = null; |
| |
| try |
| { |
| backupHelper = new DbBackup(environment); |
| |
| // Prevent BDB from writing to its log files while the backup it taken. |
| backupHelper.startBackup(); |
| |
| // Back up the BDB log files to the destination directory. |
| String[] filesForBackup = backupHelper.getLogFilesInBackupSet(); |
| |
| for (int i = 0; i < filesForBackup.length; i++) |
| { |
| File sourceFile = new File(fromdir + File.separator + filesForBackup[i]); |
| File destFile = new File(todir + File.separator + filesForBackup[i]); |
| FileUtils.copy(sourceFile, destFile); |
| } |
| } |
| finally |
| { |
| // Remember to exit backup mode, or all log files won't be cleaned and disk usage will bloat. |
| if (backupHelper != null) |
| { |
| backupHelper.endBackup(); |
| } |
| } |
| } |
| |
| /** |
| * Takes a hot backup when another process has locked the BDB database. |
| * |
| * @param fromdir The source directory path. |
| * @param todir The destination directory path. |
| * |
| * @return A list of all of the names of the files succesfully backed up. |
| */ |
| public String[] takeBackupNoLock(String fromdir, String todir) |
| { |
| if (log.isDebugEnabled()) |
| { |
| log.debug("public void takeBackupNoLock(String fromdir = " + fromdir + ", String todir = " + todir |
| + "): called"); |
| } |
| |
| File fromDirFile = new File(fromdir); |
| |
| if (!fromDirFile.isDirectory()) |
| { |
| throw new IllegalArgumentException("The specified fromdir(" + fromdir |
| + ") must be the directory containing your bdbstore."); |
| } |
| |
| File toDirFile = new File(todir); |
| |
| if (!toDirFile.exists()) |
| { |
| // Create directory if it doesn't exist |
| toDirFile.mkdirs(); |
| |
| if (log.isDebugEnabled()) |
| { |
| log.debug("Created backup directory:" + toDirFile); |
| } |
| } |
| |
| if (!toDirFile.isDirectory()) |
| { |
| throw new IllegalArgumentException("The specified todir(" + todir + ") must be a directory."); |
| } |
| |
| // Repeat until manage to open consistent set of files for reading. |
| boolean consistentSet = false; |
| FileInputStream[] fileInputStreams = new FileInputStream[0]; |
| File[] fileSet = new File[0]; |
| long start = System.currentTimeMillis(); |
| |
| while (!consistentSet) |
| { |
| // List all .jdb files in the directory. |
| fileSet = fromDirFile.listFiles(new FilenameFilter() |
| { |
| @Override |
| public boolean accept(File dir, String name) |
| { |
| return name.endsWith(LOG_FILE_SUFFIX); |
| } |
| }); |
| |
| if (fileSet == null || fileSet.length == 0) |
| { |
| throw new StoreException("There are no BDB log files to backup in the '" + fromdir + "' directory."); |
| } |
| |
| // The files must be copied in alphabetical order (numerical in effect) |
| Arrays.sort(fileSet); |
| |
| // Open them all for reading. |
| fileInputStreams = new FileInputStream[fileSet.length]; |
| |
| |
| for (int i = 0; i < fileSet.length; i++) |
| { |
| try |
| { |
| fileInputStreams[i] = new FileInputStream(fileSet[i]); |
| } |
| catch (FileNotFoundException e) |
| { |
| // Close any files opened for reading so far. |
| for (int j = 0; j < i; j++) |
| { |
| if (fileInputStreams[j] != null) |
| { |
| try |
| { |
| fileInputStreams[j].close(); |
| } |
| catch (IOException ioEx) |
| { |
| // Rethrow this as a runtime exception, as something strange has happened. |
| throw new StoreException(ioEx); |
| } |
| } |
| } |
| |
| // Could not open a consistent file set so try again. |
| break; |
| } |
| |
| // A consistent set has been opened if all files were successfully opened for reading. |
| if (i == (fileSet.length - 1)) |
| { |
| consistentSet = true; |
| } |
| } |
| |
| // Check that the script has not timed out, and raise an error if it has. |
| long now = System.currentTimeMillis(); |
| if ((now - start) > TIMEOUT) |
| { |
| throw new StoreException("Hot backup script failed to complete in " + (TIMEOUT / 1000) + " seconds."); |
| } |
| } |
| |
| // Copy the consistent set of open files. |
| List<String> backedUpFileNames = new LinkedList<String>(); |
| |
| for (int j = 0; j < fileSet.length; j++) |
| { |
| File destFile = new File(todir + File.separator + fileSet[j].getName()); |
| try |
| { |
| Files.copy(fileInputStreams[j], destFile.toPath()); |
| } |
| catch (IOException ioe) |
| { |
| throw new StoreException(ioe.getMessage() + " fromDir:" + fromdir + " toDir:" + toDirFile, ioe); |
| } |
| |
| backedUpFileNames.add(destFile.getName()); |
| |
| // Close all of the files. |
| try |
| { |
| fileInputStreams[j].close(); |
| } |
| catch (IOException e) |
| { |
| // Rethrow this as a runtime exception, as something strange has happened. |
| throw new StoreException(e); |
| } |
| } |
| |
| return backedUpFileNames.toArray(new String[backedUpFileNames.size()]); |
| } |
| |
| /* |
| * Creates an environment for the bdb log files in the specified directory. This envrinonment can only be used |
| * to backup these files, if they are not locked by another database instance. |
| * |
| * @param fromdir The path to the directory to create the environment for. |
| * |
| * @throws DatabaseException Any underlying exceptions from BDB are allowed to fall through. |
| */ |
| private Environment createSourceDirEnvironment(String fromdir) throws DatabaseException |
| { |
| // Initialize the BDB backup utility on the source directory. |
| return new Environment(new File(fromdir), new EnvironmentConfig()); |
| } |
| } |