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