1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. 2 // This source code is licensed under both the GPLv2 (found in the 3 // COPYING file in the root directory) and Apache 2.0 License 4 // (found in the LICENSE.Apache file in the root directory). 5 6 package org.rocksdb; 7 8 import org.junit.ClassRule; 9 import org.junit.Rule; 10 import org.junit.Test; 11 import org.junit.rules.TemporaryFolder; 12 13 import java.util.List; 14 import java.util.concurrent.ThreadLocalRandom; 15 16 import static org.assertj.core.api.Assertions.assertThat; 17 18 public class BackupEngineTest { 19 20 @ClassRule 21 public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = 22 new RocksNativeLibraryResource(); 23 24 @Rule 25 public TemporaryFolder dbFolder = new TemporaryFolder(); 26 27 @Rule 28 public TemporaryFolder backupFolder = new TemporaryFolder(); 29 30 @Test backupDb()31 public void backupDb() throws RocksDBException { 32 // Open empty database. 33 try(final Options opt = new Options().setCreateIfMissing(true); 34 final RocksDB db = RocksDB.open(opt, 35 dbFolder.getRoot().getAbsolutePath())) { 36 37 // Fill database with some test values 38 prepareDatabase(db); 39 40 // Create two backups 41 try(final BackupableDBOptions bopt = new BackupableDBOptions( 42 backupFolder.getRoot().getAbsolutePath()); 43 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 44 be.createNewBackup(db, false); 45 be.createNewBackup(db, true); 46 verifyNumberOfValidBackups(be, 2); 47 } 48 } 49 } 50 51 @Test deleteBackup()52 public void deleteBackup() throws RocksDBException { 53 // Open empty database. 54 try(final Options opt = new Options().setCreateIfMissing(true); 55 final RocksDB db = RocksDB.open(opt, 56 dbFolder.getRoot().getAbsolutePath())) { 57 // Fill database with some test values 58 prepareDatabase(db); 59 // Create two backups 60 try(final BackupableDBOptions bopt = new BackupableDBOptions( 61 backupFolder.getRoot().getAbsolutePath()); 62 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 63 be.createNewBackup(db, false); 64 be.createNewBackup(db, true); 65 final List<BackupInfo> backupInfo = 66 verifyNumberOfValidBackups(be, 2); 67 // Delete the first backup 68 be.deleteBackup(backupInfo.get(0).backupId()); 69 final List<BackupInfo> newBackupInfo = 70 verifyNumberOfValidBackups(be, 1); 71 72 // The second backup must remain. 73 assertThat(newBackupInfo.get(0).backupId()). 74 isEqualTo(backupInfo.get(1).backupId()); 75 } 76 } 77 } 78 79 @Test purgeOldBackups()80 public void purgeOldBackups() throws RocksDBException { 81 // Open empty database. 82 try(final Options opt = new Options().setCreateIfMissing(true); 83 final RocksDB db = RocksDB.open(opt, 84 dbFolder.getRoot().getAbsolutePath())) { 85 // Fill database with some test values 86 prepareDatabase(db); 87 // Create four backups 88 try(final BackupableDBOptions bopt = new BackupableDBOptions( 89 backupFolder.getRoot().getAbsolutePath()); 90 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 91 be.createNewBackup(db, false); 92 be.createNewBackup(db, true); 93 be.createNewBackup(db, true); 94 be.createNewBackup(db, true); 95 final List<BackupInfo> backupInfo = 96 verifyNumberOfValidBackups(be, 4); 97 // Delete everything except the latest backup 98 be.purgeOldBackups(1); 99 final List<BackupInfo> newBackupInfo = 100 verifyNumberOfValidBackups(be, 1); 101 // The latest backup must remain. 102 assertThat(newBackupInfo.get(0).backupId()). 103 isEqualTo(backupInfo.get(3).backupId()); 104 } 105 } 106 } 107 108 @Test restoreLatestBackup()109 public void restoreLatestBackup() throws RocksDBException { 110 try(final Options opt = new Options().setCreateIfMissing(true)) { 111 // Open empty database. 112 RocksDB db = null; 113 try { 114 db = RocksDB.open(opt, 115 dbFolder.getRoot().getAbsolutePath()); 116 // Fill database with some test values 117 prepareDatabase(db); 118 119 try (final BackupableDBOptions bopt = new BackupableDBOptions( 120 backupFolder.getRoot().getAbsolutePath()); 121 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 122 be.createNewBackup(db, true); 123 verifyNumberOfValidBackups(be, 1); 124 db.put("key1".getBytes(), "valueV2".getBytes()); 125 db.put("key2".getBytes(), "valueV2".getBytes()); 126 be.createNewBackup(db, true); 127 verifyNumberOfValidBackups(be, 2); 128 db.put("key1".getBytes(), "valueV3".getBytes()); 129 db.put("key2".getBytes(), "valueV3".getBytes()); 130 assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); 131 assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); 132 133 db.close(); 134 db = null; 135 136 verifyNumberOfValidBackups(be, 2); 137 // restore db from latest backup 138 try(final RestoreOptions ropts = new RestoreOptions(false)) { 139 be.restoreDbFromLatestBackup(dbFolder.getRoot().getAbsolutePath(), 140 dbFolder.getRoot().getAbsolutePath(), ropts); 141 } 142 143 // Open database again. 144 db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath()); 145 146 // Values must have suffix V2 because of restoring latest backup. 147 assertThat(new String(db.get("key1".getBytes()))).endsWith("V2"); 148 assertThat(new String(db.get("key2".getBytes()))).endsWith("V2"); 149 } 150 } finally { 151 if(db != null) { 152 db.close(); 153 } 154 } 155 } 156 } 157 158 @Test restoreFromBackup()159 public void restoreFromBackup() 160 throws RocksDBException { 161 try(final Options opt = new Options().setCreateIfMissing(true)) { 162 RocksDB db = null; 163 try { 164 // Open empty database. 165 db = RocksDB.open(opt, 166 dbFolder.getRoot().getAbsolutePath()); 167 // Fill database with some test values 168 prepareDatabase(db); 169 try (final BackupableDBOptions bopt = new BackupableDBOptions( 170 backupFolder.getRoot().getAbsolutePath()); 171 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 172 be.createNewBackup(db, true); 173 verifyNumberOfValidBackups(be, 1); 174 db.put("key1".getBytes(), "valueV2".getBytes()); 175 db.put("key2".getBytes(), "valueV2".getBytes()); 176 be.createNewBackup(db, true); 177 verifyNumberOfValidBackups(be, 2); 178 db.put("key1".getBytes(), "valueV3".getBytes()); 179 db.put("key2".getBytes(), "valueV3".getBytes()); 180 assertThat(new String(db.get("key1".getBytes()))).endsWith("V3"); 181 assertThat(new String(db.get("key2".getBytes()))).endsWith("V3"); 182 183 //close the database 184 db.close(); 185 db = null; 186 187 //restore the backup 188 final List<BackupInfo> backupInfo = verifyNumberOfValidBackups(be, 2); 189 // restore db from first backup 190 be.restoreDbFromBackup(backupInfo.get(0).backupId(), 191 dbFolder.getRoot().getAbsolutePath(), 192 dbFolder.getRoot().getAbsolutePath(), 193 new RestoreOptions(false)); 194 // Open database again. 195 db = RocksDB.open(opt, 196 dbFolder.getRoot().getAbsolutePath()); 197 // Values must have suffix V2 because of restoring latest backup. 198 assertThat(new String(db.get("key1".getBytes()))).endsWith("V1"); 199 assertThat(new String(db.get("key2".getBytes()))).endsWith("V1"); 200 } 201 } finally { 202 if(db != null) { 203 db.close(); 204 } 205 } 206 } 207 } 208 209 @Test backupDbWithMetadata()210 public void backupDbWithMetadata() throws RocksDBException { 211 // Open empty database. 212 try (final Options opt = new Options().setCreateIfMissing(true); 213 final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { 214 // Fill database with some test values 215 prepareDatabase(db); 216 217 // Create two backups 218 try (final BackupableDBOptions bopt = 219 new BackupableDBOptions(backupFolder.getRoot().getAbsolutePath()); 220 final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { 221 final String metadata = String.valueOf(ThreadLocalRandom.current().nextInt()); 222 be.createNewBackupWithMetadata(db, metadata, true); 223 final List<BackupInfo> backupInfoList = verifyNumberOfValidBackups(be, 1); 224 assertThat(backupInfoList.get(0).appMetadata()).isEqualTo(metadata); 225 } 226 } 227 } 228 229 /** 230 * Verify backups. 231 * 232 * @param be {@link BackupEngine} instance. 233 * @param expectedNumberOfBackups numerical value 234 * @throws RocksDBException thrown if an error occurs within the native 235 * part of the library. 236 */ verifyNumberOfValidBackups(final BackupEngine be, final int expectedNumberOfBackups)237 private List<BackupInfo> verifyNumberOfValidBackups(final BackupEngine be, 238 final int expectedNumberOfBackups) throws RocksDBException { 239 // Verify that backups exist 240 assertThat(be.getCorruptedBackups().length). 241 isEqualTo(0); 242 be.garbageCollect(); 243 final List<BackupInfo> backupInfo = be.getBackupInfo(); 244 assertThat(backupInfo.size()). 245 isEqualTo(expectedNumberOfBackups); 246 return backupInfo; 247 } 248 249 /** 250 * Fill database with some test values. 251 * 252 * @param db {@link RocksDB} instance. 253 * @throws RocksDBException thrown if an error occurs within the native 254 * part of the library. 255 */ prepareDatabase(final RocksDB db)256 private void prepareDatabase(final RocksDB db) 257 throws RocksDBException { 258 db.put("key1".getBytes(), "valueV1".getBytes()); 259 db.put("key2".getBytes(), "valueV1".getBytes()); 260 } 261 } 262