blob: 6084ae96cd5cca0a6e7e1c7735e952bd48eb980d [file] [log] [blame]
/*
* 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.hadoop.hdds.utils.db;
import javax.management.MBeanServer;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.ColumnFamilyOptions;
import org.rocksdb.DBOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.Statistics;
import org.rocksdb.StatsLevel;
/**
* RDBStore Tests.
*/
public class TestRDBStore {
private final List<String> families =
Arrays.asList(DFSUtil.bytes2String(RocksDB.DEFAULT_COLUMN_FAMILY),
"First", "Second", "Third",
"Fourth", "Fifth",
"Sixth");
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
private RDBStore rdbStore = null;
private DBOptions options = null;
private Set<TableConfig> configSet;
@Before
public void setUp() throws Exception {
options = new DBOptions();
options.setCreateIfMissing(true);
options.setCreateMissingColumnFamilies(true);
Statistics statistics = new Statistics();
statistics.setStatsLevel(StatsLevel.ALL);
options = options.setStatistics(statistics);
configSet = new HashSet<>();
for(String name : families) {
TableConfig newConfig = new TableConfig(name, new ColumnFamilyOptions());
configSet.add(newConfig);
}
rdbStore = new RDBStore(folder.newFolder(), options, configSet);
}
@After
public void tearDown() throws Exception {
if (rdbStore != null) {
rdbStore.close();
}
}
private void insertRandomData(RDBStore dbStore, int familyIndex)
throws Exception {
try (Table firstTable = dbStore.getTable(families.get(familyIndex))) {
Assert.assertNotNull("Table cannot be null", firstTable);
for (int x = 0; x < 100; x++) {
byte[] key =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
byte[] value =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
firstTable.put(key, value);
}
}
}
@Test
public void compactDB() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
Assert.assertNotNull("DB Store cannot be null", newStore);
insertRandomData(newStore, 1);
// This test does not assert anything if there is any error this test
// will throw and fail.
newStore.compactDB();
}
}
@Test
public void close() throws Exception {
RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet);
Assert.assertNotNull("DBStore cannot be null", newStore);
// This test does not assert anything if there is any error this test
// will throw and fail.
newStore.close();
}
@Test
public void moveKey() throws Exception {
byte[] key =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
byte[] value =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
try (Table firstTable = rdbStore.getTable(families.get(1))) {
firstTable.put(key, value);
try (Table<byte[], byte[]> secondTable = rdbStore
.getTable(families.get(2))) {
rdbStore.move(key, firstTable, secondTable);
byte[] newvalue = secondTable.get(key);
// Make sure we have value in the second table
Assert.assertNotNull(newvalue);
//and it is same as what we wrote to the FirstTable
Assert.assertArrayEquals(value, newvalue);
}
// After move this key must not exist in the first table.
Assert.assertNull(firstTable.get(key));
}
}
@Test
public void moveWithValue() throws Exception {
byte[] key =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
byte[] value =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
byte[] nextValue =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
try (Table firstTable = rdbStore.getTable(families.get(1))) {
firstTable.put(key, value);
try (Table<byte[], byte[]> secondTable = rdbStore
.getTable(families.get(2))) {
rdbStore.move(key, nextValue, firstTable, secondTable);
byte[] newvalue = secondTable.get(key);
// Make sure we have value in the second table
Assert.assertNotNull(newvalue);
//and it is not same as what we wrote to the FirstTable, and equals
// the new value.
Assert.assertArrayEquals(nextValue, nextValue);
}
}
}
@Test
public void getEstimatedKeyCount() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
Assert.assertNotNull("DB Store cannot be null", newStore);
// Write 100 keys to the first table.
insertRandomData(newStore, 1);
// Write 100 keys to the secondTable table.
insertRandomData(newStore, 2);
// Let us make sure that our estimate is not off by 10%
Assert.assertTrue(newStore.getEstimatedKeyCount() > 180
|| newStore.getEstimatedKeyCount() < 220);
}
}
@Test
public void getStatMBeanName() throws Exception {
try (Table firstTable = rdbStore.getTable(families.get(1))) {
for (int y = 0; y < 100; y++) {
byte[] key =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
byte[] value =
RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
firstTable.put(key, value);
}
}
MBeanServer platformMBeanServer =
ManagementFactory.getPlatformMBeanServer();
Thread.sleep(2000);
Object keysWritten = platformMBeanServer
.getAttribute(rdbStore.getStatMBeanName(), "NUMBER_KEYS_WRITTEN");
Assert.assertTrue(((Long) keysWritten) >= 99L);
Object dbWriteAverage = platformMBeanServer
.getAttribute(rdbStore.getStatMBeanName(), "DB_WRITE_AVERAGE");
Assert.assertTrue((double) dbWriteAverage > 0);
}
@Test
public void getTable() throws Exception {
for (String tableName : families) {
try (Table table = rdbStore.getTable(tableName)) {
Assert.assertNotNull(tableName + "is null", table);
}
}
thrown.expect(IOException.class);
rdbStore.getTable("ATableWithNoName");
}
@Test
public void listTables() throws Exception {
List<Table> tableList = rdbStore.listTables();
Assert.assertNotNull("Table list cannot be null", tableList);
Map<String, Table> hashTable = new HashMap<>();
for (Table t : tableList) {
hashTable.put(t.getName(), t);
}
int count = families.size();
// Assert that we have all the tables in the list and no more.
for (String name : families) {
Assert.assertTrue(hashTable.containsKey(name));
count--;
}
Assert.assertEquals(0, count);
}
@Test
public void testRocksDBCheckpoint() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
Assert.assertNotNull("DB Store cannot be null", newStore);
insertRandomData(newStore, 1);
DBCheckpoint checkpoint =
newStore.getCheckpoint(true);
Assert.assertNotNull(checkpoint);
RDBStore restoredStoreFromCheckPoint =
new RDBStore(checkpoint.getCheckpointLocation().toFile(),
options, configSet);
// Let us make sure that our estimate is not off by 10%
Assert.assertTrue(
restoredStoreFromCheckPoint.getEstimatedKeyCount() > 90
|| restoredStoreFromCheckPoint.getEstimatedKeyCount() < 110);
checkpoint.cleanupCheckpoint();
}
}
@Test
public void testRocksDBCheckpointCleanup() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
Assert.assertNotNull("DB Store cannot be null", newStore);
insertRandomData(newStore, 1);
DBCheckpoint checkpoint =
newStore.getCheckpoint(true);
Assert.assertNotNull(checkpoint);
Assert.assertTrue(Files.exists(
checkpoint.getCheckpointLocation()));
checkpoint.cleanupCheckpoint();
Assert.assertFalse(Files.exists(
checkpoint.getCheckpointLocation()));
}
}
/**
* Not strictly a unit test. Just a confirmation of the expected behavior
* of RocksDB keyMayExist API.
* Expected behavior - On average, keyMayExist latency < key.get() latency
* for invalid keys.
* @throws Exception if unable to read from RocksDB.
*/
@Test
public void testRocksDBKeyMayExistApi() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
RocksDB db = newStore.getDb();
//Test with 50 invalid keys.
long start = System.nanoTime();
for (int i = 0; i < 50; i++) {
Assert.assertTrue(db.get(
StringUtils.getBytesUtf16("key" + i))== null);
}
long end = System.nanoTime();
long keyGetLatency = end - start;
start = System.nanoTime();
for (int i = 0; i < 50; i++) {
Assert.assertFalse(db.keyMayExist(
StringUtils.getBytesUtf16("key" + i), new StringBuilder()));
}
end = System.nanoTime();
long keyMayExistLatency = end - start;
Assert.assertTrue(keyMayExistLatency < keyGetLatency);
}
}
@Test
public void testGetDBUpdatesSince() throws Exception {
try (RDBStore newStore =
new RDBStore(folder.newFolder(), options, configSet)) {
try (Table firstTable = newStore.getTable(families.get(1))) {
firstTable.put(StringUtils.getBytesUtf16("Key1"), StringUtils
.getBytesUtf16("Value1"));
firstTable.put(StringUtils.getBytesUtf16("Key2"), StringUtils
.getBytesUtf16("Value2"));
}
Assert.assertTrue(
newStore.getDb().getLatestSequenceNumber() == 2);
DBUpdatesWrapper dbUpdatesSince = newStore.getUpdatesSince(0);
Assert.assertEquals(2, dbUpdatesSince.getData().size());
}
}
}