content: // Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
  //  This source code is licensed under both the GPLv2 (found in the
  //  COPYING file in the root directory) and Apache 2.0 License
  //  (found in the LICENSE.Apache file in the root directory).
  
  package org.rocksdb;
  
  import org.junit.ClassRule;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.rules.TemporaryFolder;
  
  import java.util.List;
  
  import static org.assertj.core.api.Assertions.assertThat;
  
  public class BackupEngineTest {
  
    @ClassRule
    public static final RocksMemoryResource rocksMemoryResource =
        new RocksMemoryResource();
  
    @Rule
    public TemporaryFolder dbFolder = new TemporaryFolder();
  
    @Rule
    public TemporaryFolder backupFolder = new TemporaryFolder();
  
    @Test
    public void backupDb() throws RocksDBException {
      // Open empty database.
      try(final Options opt = new Options().setCreateIfMissing(true);
          final RocksDB db = RocksDB.open(opt,
              dbFolder.getRoot().getAbsolutePath())) {
  
        // Fill database with some test values
        prepareDatabase(db);
  
        // Create two backups
        try(final BackupableDBOptions bopt = new BackupableDBOptions(
            backupFolder.getRoot().getAbsolutePath());
            final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
          be.createNewBackup(db, false);
          be.createNewBackup(db, true);
          verifyNumberOfValidBackups(be, 2);
        }
      }
    }
  
    @Test
    public void deleteBackup() throws RocksDBException {
      // Open empty database.
      try(final Options opt = new Options().setCreateIfMissing(true);
          final RocksDB db = RocksDB.open(opt,
              dbFolder.getRoot().getAbsolutePath())) {
        // Fill database with some test values
        prepareDatabase(db);
        // Create two backups
        try(final BackupableDBOptions bopt = new BackupableDBOptions(
            backupFolder.getRoot().getAbsolutePath());
            final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
          be.createNewBackup(db, false);
          be.createNewBackup(db, true);
          final List<BackupInfo> backupInfo =
              verifyNumberOfValidBackups(be, 2);
          // Delete the first backup
          be.deleteBackup(backupInfo.get(0).backupId());
          final List<BackupInfo> newBackupInfo =
              verifyNumberOfValidBackups(be, 1);
  
          // The second backup must remain.
          assertThat(newBackupInfo.get(0).backupId()).
              isEqualTo(backupInfo.get(1).backupId());
        }
      }
    }
  
    @Test
    public void purgeOldBackups() throws RocksDBException {
      // Open empty database.
      try(final Options opt = new Options().setCreateIfMissing(true);
          final RocksDB db = RocksDB.open(opt,
              dbFolder.getRoot().getAbsolutePath())) {
        // Fill database with some test values
        prepareDatabase(db);
        // Create four backups
        try(final BackupableDBOptions bopt = new BackupableDBOptions(
            backupFolder.getRoot().getAbsolutePath());
            final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
          be.createNewBackup(db, false);
          be.createNewBackup(db, true);
          be.createNewBackup(db, true);
          be.createNewBackup(db, true);
          final List<BackupInfo> backupInfo =
              verifyNumberOfValidBackups(be, 4);
          // Delete everything except the latest backup
          be.purgeOldBackups(1);
          final List<BackupInfo> newBackupInfo =
              verifyNumberOfValidBackups(be, 1);
          // The latest backup must remain.
          assertThat(newBackupInfo.get(0).backupId()).
              isEqualTo(backupInfo.get(3).backupId());
        }
      }
    }
  
    @Test
    public void restoreLatestBackup() throws RocksDBException {
      try(final Options opt = new Options().setCreateIfMissing(true)) {
        // Open empty database.
        RocksDB db = null;
        try {
          db = RocksDB.open(opt,
              dbFolder.getRoot().getAbsolutePath());
          // Fill database with some test values
          prepareDatabase(db);
  
          try (final BackupableDBOptions bopt = new BackupableDBOptions(
              backupFolder.getRoot().getAbsolutePath());
               final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
            be.createNewBackup(db, true);
            verifyNumberOfValidBackups(be, 1);
            db.put("key1".getBytes(), "valueV2".getBytes());
            db.put("key2".getBytes(), "valueV2".getBytes());
            be.createNewBackup(db, true);
            verifyNumberOfValidBackups(be, 2);
            db.put("key1".getBytes(), "valueV3".getBytes());
            db.put("key2".getBytes(), "valueV3".getBytes());
            assertThat(new String(db.get("key1".getBytes()))).endsWith("V3");
            assertThat(new String(db.get("key2".getBytes()))).endsWith("V3");
  
            db.close();
            db = null;
  
            verifyNumberOfValidBackups(be, 2);
            // restore db from latest backup
            try(final RestoreOptions ropts = new RestoreOptions(false)) {
              be.restoreDbFromLatestBackup(dbFolder.getRoot().getAbsolutePath(),
                  dbFolder.getRoot().getAbsolutePath(), ropts);
            }
  
            // Open database again.
            db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath());
  
            // Values must have suffix V2 because of restoring latest backup.
            assertThat(new String(db.get("key1".getBytes()))).endsWith("V2");
            assertThat(new String(db.get("key2".getBytes()))).endsWith("V2");
          }
        } finally {
          if(db != null) {
            db.close();
          }
        }
      }
    }
  
    @Test
    public void restoreFromBackup()
        throws RocksDBException {
      try(final Options opt = new Options().setCreateIfMissing(true)) {
        RocksDB db = null;
        try {
          // Open empty database.
          db = RocksDB.open(opt,
              dbFolder.getRoot().getAbsolutePath());
          // Fill database with some test values
          prepareDatabase(db);
          try (final BackupableDBOptions bopt = new BackupableDBOptions(
              backupFolder.getRoot().getAbsolutePath());
               final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) {
            be.createNewBackup(db, true);
            verifyNumberOfValidBackups(be, 1);
            db.put("key1".getBytes(), "valueV2".getBytes());
            db.put("key2".getBytes(), "valueV2".getBytes());
            be.createNewBackup(db, true);
            verifyNumberOfValidBackups(be, 2);
            db.put("key1".getBytes(), "valueV3".getBytes());
            db.put("key2".getBytes(), "valueV3".getBytes());
            assertThat(new String(db.get("key1".getBytes()))).endsWith("V3");
            assertThat(new String(db.get("key2".getBytes()))).endsWith("V3");
  
            //close the database
            db.close();
            db = null;
  
            //restore the backup
            final List<BackupInfo> backupInfo = verifyNumberOfValidBackups(be, 2);
            // restore db from first backup
            be.restoreDbFromBackup(backupInfo.get(0).backupId(),
                dbFolder.getRoot().getAbsolutePath(),
                dbFolder.getRoot().getAbsolutePath(),
                new RestoreOptions(false));
            // Open database again.
            db = RocksDB.open(opt,
                dbFolder.getRoot().getAbsolutePath());
            // Values must have suffix V2 because of restoring latest backup.
            assertThat(new String(db.get("key1".getBytes()))).endsWith("V1");
            assertThat(new String(db.get("key2".getBytes()))).endsWith("V1");
          }
        } finally {
          if(db != null) {
            db.close();
          }
        }
      }
    }
  
    /**
     * Verify backups.
     *
     * @param be {@link BackupEngine} instance.
     * @param expectedNumberOfBackups numerical value
     * @throws RocksDBException thrown if an error occurs within the native
     *     part of the library.
     */
    private List<BackupInfo> verifyNumberOfValidBackups(final BackupEngine be,
        final int expectedNumberOfBackups) throws RocksDBException {
      // Verify that backups exist
      assertThat(be.getCorruptedBackups().length).
          isEqualTo(0);
      be.garbageCollect();
      final List<BackupInfo> backupInfo = be.getBackupInfo();
      assertThat(backupInfo.size()).
          isEqualTo(expectedNumberOfBackups);
      return backupInfo;
    }
  
    /**
     * Fill database with some test values.
     *
     * @param db {@link RocksDB} instance.
     * @throws RocksDBException thrown if an error occurs within the native
     *     part of the library.
     */
    private void prepareDatabase(final RocksDB db)
        throws RocksDBException {
      db.put("key1".getBytes(), "valueV1".getBytes());
      db.put("key2".getBytes(), "valueV1".getBytes());
    }
  }
  