| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.je.util; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import com.sleepycat.je.BackupArchiveLocation; |
| import com.sleepycat.je.BackupFSArchiveCopy; |
| import com.sleepycat.je.BackupFileCopy; |
| import com.sleepycat.je.BackupFileLocation; |
| import com.sleepycat.je.JEVersion; |
| import com.sleepycat.je.dbi.BackupManager; |
| import com.sleepycat.je.dbi.SnapshotManifest; |
| import com.sleepycat.je.dbi.SnapshotManifest.LogFileInfo; |
| import com.sleepycat.je.utilint.CmdUtil; |
| |
| /** |
| * @hidden For internal use: automatic backups |
| * |
| * CheckSnapshot checks the validity of an archived backup snapshot. |
| * |
| * <pre> |
| * usage: java { com.sleepycat.je.util.CheckSnapshot | |
| * -jar je-<version>.jar CheckSnapshot } |
| * -m <file> # the manifest of the snapshot to check |
| * -c <file> # the copy class configuration |
| * -l <file> # the location class configuration |
| * [-C <class>] # the copy class |
| * [-L <class>] # the location class |
| * [-V] # print version |
| * </pre> |
| */ |
| public class CheckSnapshot { |
| |
| /* TODO: Add this class to JarMain when we make it public */ |
| |
| private static final String USAGE = |
| "usage: " + CmdUtil.getJavaCommand(CheckSnapshot.class) + "\n" + |
| " -m <file> # the manifest of the snapshot to check\n" + |
| " -c <file> # the copy class configuration\n" + |
| " -l <file> # the location class configuration\n" + |
| " [-C <class>] # the copy class\n" + |
| " [-L <class>] # the location class\n" + |
| " [-V] # print version"; |
| |
| private SnapshotManifest manifest; |
| private BackupFileCopy backupCopy; |
| private BackupArchiveLocation backupLocation; |
| |
| /** |
| * Creates a CheckSnapshot object for checking the validity of an archived |
| * backup snapshot. |
| * |
| * @param manifest the manifest of the snapshot to check |
| * @param backupCopy used to check files in the backup archive |
| * @param backupLocation used to identify the location of files in the |
| * backup archive |
| */ |
| public CheckSnapshot(final SnapshotManifest manifest, |
| final BackupFileCopy backupCopy, |
| final BackupArchiveLocation backupLocation) { |
| this.manifest = requireNonNull(manifest); |
| this.backupCopy = requireNonNull(backupCopy); |
| this.backupLocation = requireNonNull(backupLocation); |
| } |
| |
| private CheckSnapshot() { } |
| |
| /** |
| * Checks the validity of an archived backup snapshot. Prints the results |
| * of the check to standard output, and errors and usage to standard error. |
| * Exits with 0 on success and a non-zero value if there are failures. |
| * |
| * @param argv the arguments |
| * @see #USAGE |
| */ |
| public static void main(final String[] argv) { |
| try { |
| mainInternal(System.out, argv); |
| } catch (UsageException e) { |
| System.err.println(e.getMessage()); |
| } catch (RuntimeException e) { |
| System.err.println(e.getMessage()); |
| System.exit(1); |
| } |
| } |
| |
| /** |
| * Checks the validity of an archived backup snapshot, for testing. Prints |
| * the results of the check to specified stream. Throws an exception if |
| * there are failures or if a usage message should be printed. |
| * |
| * @param argv the arguments |
| * @throws UsageException if a usage message should be printed |
| * @throws RuntimeException if the check fails |
| * @see #USAGE |
| */ |
| static void mainInternal(final PrintStream out, final String... argv) { |
| final CheckSnapshot checkSnapshot = new CheckSnapshot(); |
| checkSnapshot.parseArgs(argv); |
| final SnapshotManifest manifest = checkSnapshot.manifest; |
| out.println("Checking snapshot " + manifest.getSnapshot() + |
| " for node " + manifest.getNodeName()); |
| out.println("Snapshot is " + |
| (manifest.getIsComplete() ? "complete" : "not complete")); |
| out.println(checkSnapshot.check()); |
| out.println("Check passed"); |
| } |
| |
| private void printUsage(String msg) { |
| if (msg == null) { |
| throw new UsageException(USAGE); |
| } |
| throw new RuntimeException(msg + "\n" + USAGE); |
| } |
| |
| static class UsageException extends RuntimeException { |
| private static final long serialVersionUID = 0; |
| UsageException(final String msg) { |
| super(msg); |
| } |
| } |
| |
| private void parseArgs(final String[] argv) { |
| int argc = 0; |
| int nArgs = argv.length; |
| |
| if (nArgs == 0) { |
| printUsage(null); |
| } |
| |
| Path manifestPath = null; |
| Path copyConfig = null; |
| Path locationConfig = null; |
| String copyClass = null; |
| String locationClass = null; |
| |
| while (argc < nArgs) { |
| final String thisArg = argv[argc++]; |
| if (thisArg.equals("-m")) { |
| if (argc < nArgs) { |
| manifestPath = Paths.get(argv[argc++]); |
| } else { |
| printUsage("-m requires an argument"); |
| } |
| } else if (thisArg.equals("-c")) { |
| if (argc < nArgs) { |
| copyConfig = Paths.get(argv[argc++]); |
| } else { |
| printUsage("-c requires an argument"); |
| } |
| } else if (thisArg.equals("-l")) { |
| if (argc < nArgs) { |
| locationConfig = Paths.get(argv[argc++]); |
| } else { |
| printUsage("-l requires an argument"); |
| } |
| } else if (thisArg.equals("-C")) { |
| if (argc < nArgs) { |
| copyClass = argv[argc++]; |
| } else { |
| printUsage("-C requires an argument"); |
| } |
| } else if (thisArg.equals("-L")) { |
| if (argc < nArgs) { |
| locationClass = argv[argc++]; |
| } else { |
| printUsage("-L requires an argument"); |
| } |
| } else if (thisArg.equals("-V")) { |
| throw new UsageException(JEVersion.CURRENT_VERSION.toString()); |
| } else { |
| printUsage("Unknown argument: " + thisArg); |
| } |
| } |
| |
| if (manifestPath == null) { |
| printUsage("-m is a required argument"); |
| } |
| if (copyConfig == null) { |
| printUsage("-c is a required argument"); |
| } |
| if (locationConfig == null) { |
| printUsage("-l is a required argument"); |
| } |
| try { |
| manifest = readManifest(manifestPath); |
| } catch (IOException e) { |
| throw new RuntimeException( |
| "Problem reading manifest file " + manifestPath + ": " + e); |
| } |
| try { |
| backupCopy = (copyClass != null) ? |
| BackupManager.getImplementationInstance( |
| BackupFileCopy.class, copyClass) : |
| new BackupFSArchiveCopy(); |
| } catch (RuntimeException e) { |
| throw new RuntimeException("Problem with copy class " + |
| copyClass + ": " + e); |
| } |
| try { |
| backupCopy.initialize(copyConfig.toFile()); |
| } catch (Exception e) { |
| throw new RuntimeException( |
| "Problem initializing copy class from " + copyConfig + ": " + |
| e); |
| } |
| try { |
| backupLocation = (locationClass != null) ? |
| BackupManager.getImplementationInstance( |
| BackupArchiveLocation.class, locationClass) : |
| new BackupFileLocation(); |
| } catch (RuntimeException e) { |
| throw new RuntimeException("Problem with location class " + |
| locationClass + ": " + e); |
| } |
| try { |
| backupLocation.initialize(manifest.getNodeName(), |
| locationConfig.toFile()); |
| } catch (Exception e) { |
| throw new RuntimeException( |
| "Problem initializing location class from " + locationConfig + |
| ": " + e); |
| } |
| } |
| |
| /** |
| * Checks the validity of the snapshot specified in the constructor. |
| * |
| * @return status information from the check |
| * @throws RuntimeException if the check fails |
| */ |
| public String check() { |
| final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| final PrintStream out = new PrintStream(baos); |
| boolean ok = true; |
| if (!verifyFiles("snapshot", manifest.getSnapshotFiles(), out)) { |
| ok = false; |
| } |
| if (!verifyFiles("erased", manifest.getErasedFiles(), out)) { |
| ok = false; |
| } |
| final String msg; |
| try { |
| msg = baos.toString("UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| throw new RuntimeException("Unexpected exception: " + e); |
| } |
| if (!ok) { |
| throw new RuntimeException(msg); |
| } |
| return msg; |
| } |
| |
| /** |
| * Returns the manifest stored in the specified file. |
| * |
| * @param manifestPath the path of the manifest file |
| * @return the manifest |
| * @throws IOException if there is an I/O error, including if the manifest |
| * file is not found |
| */ |
| public static SnapshotManifest readManifest(final Path manifestPath) |
| throws IOException { |
| |
| return SnapshotManifest.deserialize(Files.readAllBytes(manifestPath)); |
| } |
| |
| private boolean verifyFiles(final String type, |
| final Map<String, LogFileInfo> files, |
| final PrintStream out) { |
| int copyCount = 0; |
| boolean ok = true; |
| for (final Entry<String, LogFileInfo> entry : files.entrySet()) { |
| final LogFileInfo info = entry.getValue(); |
| if (!info.getIsCopied()) { |
| continue; |
| } |
| copyCount++; |
| final String logFile = info.getSnapshot() + "/" + entry.getKey(); |
| final URL url = backupLocation.getArchiveLocation(logFile); |
| final String archiveChecksum; |
| try { |
| archiveChecksum = |
| BackupManager.checksumToHex(backupCopy.checksum(url)); |
| } catch (InterruptedException|IOException e) { |
| out.println("Error: Problem reading checksum for " + type + |
| " log file " + logFile + |
| ": " + e); |
| ok = false; |
| continue; |
| } |
| if (!info.getChecksum().equals(archiveChecksum)) { |
| out.println("Error: Incorrect checksum for " + type + |
| " log file " + logFile + |
| ": expected " + info.getChecksum()+ |
| ", found " + archiveChecksum); |
| ok = false; |
| } |
| } |
| out.println("Copied " + type + " log files: " + copyCount); |
| return ok; |
| } |
| } |