blob: fb10b1b06dba568df4442c3dfe66a8d361844cf3 [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 org.apache.hadoop.hdds.StringUtils;
import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksIterator;
import org.apache.hadoop.hdds.utils.db.managed.ManagedRocksObjectUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.mockito.stubbing.Answer;
import org.rocksdb.RocksIterator;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* This test is similar to {@link TestRDBStoreByteArrayIterator}
* except that this test is for {@link RDBStoreCodecBufferIterator}.
*/
public class TestRDBStoreCodecBufferIterator {
private RocksIterator rocksIteratorMock;
private ManagedRocksIterator managedRocksIterator;
private RDBTable rdbTableMock;
@BeforeEach
public void setup() {
CodecBuffer.enableLeakDetection();
rocksIteratorMock = mock(RocksIterator.class);
managedRocksIterator = newManagedRocksIterator();
rdbTableMock = mock(RDBTable.class);
Logger.getLogger(ManagedRocksObjectUtils.class).setLevel(Level.DEBUG);
}
ManagedRocksIterator newManagedRocksIterator() {
return new ManagedRocksIterator(rocksIteratorMock);
}
RDBStoreCodecBufferIterator newIterator() {
return new RDBStoreCodecBufferIterator(managedRocksIterator, null, null);
}
RDBStoreCodecBufferIterator newIterator(CodecBuffer prefix) {
return new RDBStoreCodecBufferIterator(
managedRocksIterator, rdbTableMock, prefix);
}
Answer<Integer> newAnswerInt(String name, int b) {
return newAnswer(name, (byte) b);
}
Answer<Integer> newAnswer(String name, byte... b) {
return invocation -> {
System.out.printf("answer %s: %s%n", name, StringUtils.bytes2Hex(b));
Object[] args = invocation.getArguments();
final ByteBuffer buffer = (ByteBuffer) args[0];
buffer.clear();
buffer.put(b);
buffer.flip();
return b.length;
};
}
@Test
public void testForEachRemaining() throws Exception {
when(rocksIteratorMock.isValid())
.thenReturn(true, true, true, true, true, true, true, false);
when(rocksIteratorMock.key(any()))
.then(newAnswerInt("key1", 0x00))
.then(newAnswerInt("key2", 0x00))
.then(newAnswerInt("key3", 0x01))
.then(newAnswerInt("key4", 0x02))
.thenThrow(new NoSuchElementException());
when(rocksIteratorMock.value(any()))
.then(newAnswerInt("val1", 0x7f))
.then(newAnswerInt("val2", 0x7f))
.then(newAnswerInt("val3", 0x7e))
.then(newAnswerInt("val4", 0x7d))
.thenThrow(new NoSuchElementException());
List<Table.KeyValue<byte[], byte[]>> remaining = new ArrayList<>();
try (RDBStoreCodecBufferIterator i = newIterator()) {
i.forEachRemaining(kv -> {
try {
remaining.add(RawKeyValue.create(
kv.getKey().getArray(), kv.getValue().getArray()));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
System.out.println("remaining: " + remaining);
assertArrayEquals(new byte[]{0x00}, remaining.get(0).getKey());
assertArrayEquals(new byte[]{0x7f}, remaining.get(0).getValue());
assertArrayEquals(new byte[]{0x01}, remaining.get(1).getKey());
assertArrayEquals(new byte[]{0x7e}, remaining.get(1).getValue());
assertArrayEquals(new byte[]{0x02}, remaining.get(2).getKey());
assertArrayEquals(new byte[]{0x7d}, remaining.get(2).getValue());
}
CodecTestUtil.gc();
}
@Test
public void testHasNextDependsOnIsvalid() throws Exception {
when(rocksIteratorMock.isValid()).thenReturn(true, true, false);
try (RDBStoreCodecBufferIterator i = newIterator()) {
assertTrue(i.hasNext());
assertFalse(i.hasNext());
}
CodecTestUtil.gc();
}
@Test
public void testNextCallsIsValidThenGetsTheValueAndStepsToNext()
throws Exception {
when(rocksIteratorMock.isValid()).thenReturn(true);
InOrder verifier = inOrder(rocksIteratorMock);
try (RDBStoreCodecBufferIterator i = newIterator()) {
i.next();
}
verifier.verify(rocksIteratorMock).isValid();
verifier.verify(rocksIteratorMock).key(any());
verifier.verify(rocksIteratorMock).value(any());
verifier.verify(rocksIteratorMock).next();
CodecTestUtil.gc();
}
@Test
public void testConstructorSeeksToFirstElement() throws Exception {
newIterator().close();
verify(rocksIteratorMock, times(1)).seekToFirst();
CodecTestUtil.gc();
}
@Test
public void testSeekToFirstSeeks() throws Exception {
try (RDBStoreCodecBufferIterator i = newIterator()) {
i.seekToFirst();
}
verify(rocksIteratorMock, times(2)).seekToFirst();
CodecTestUtil.gc();
}
@Test
public void testSeekToLastSeeks() throws Exception {
try (RDBStoreCodecBufferIterator i = newIterator()) {
i.seekToLast();
}
verify(rocksIteratorMock, times(1)).seekToLast();
CodecTestUtil.gc();
}
@Test
public void testSeekReturnsTheActualKey() throws Exception {
when(rocksIteratorMock.isValid()).thenReturn(true);
when(rocksIteratorMock.key(any()))
.then(newAnswerInt("key1", 0x00));
when(rocksIteratorMock.value(any()))
.then(newAnswerInt("val1", 0x7f));
try (RDBStoreCodecBufferIterator i = newIterator();
CodecBuffer target = CodecBuffer.wrap(new byte[]{0x55})) {
final Table.KeyValue<CodecBuffer, CodecBuffer> val = i.seek(target);
InOrder verifier = inOrder(rocksIteratorMock);
verify(rocksIteratorMock, times(1)).seekToFirst(); //at construct time
verify(rocksIteratorMock, never()).seekToLast();
verifier.verify(rocksIteratorMock, times(1))
.seek(any(ByteBuffer.class));
verifier.verify(rocksIteratorMock, times(1)).isValid();
verifier.verify(rocksIteratorMock, times(1)).key(any());
verifier.verify(rocksIteratorMock, times(1)).value(any());
assertArrayEquals(new byte[]{0x00}, val.getKey().getArray());
assertArrayEquals(new byte[]{0x7f}, val.getValue().getArray());
}
CodecTestUtil.gc();
}
@Test
public void testGettingTheKeyIfIteratorIsValid() throws Exception {
when(rocksIteratorMock.isValid()).thenReturn(true);
when(rocksIteratorMock.key(any()))
.then(newAnswerInt("key1", 0x00));
byte[] key = null;
try (RDBStoreCodecBufferIterator i = newIterator()) {
if (i.hasNext()) {
key = i.next().getKey().getArray();
}
}
InOrder verifier = inOrder(rocksIteratorMock);
verifier.verify(rocksIteratorMock, times(1)).isValid();
verifier.verify(rocksIteratorMock, times(1)).key(any());
assertArrayEquals(new byte[]{0x00}, key);
CodecTestUtil.gc();
}
@Test
public void testGettingTheValueIfIteratorIsValid() throws Exception {
when(rocksIteratorMock.isValid()).thenReturn(true);
when(rocksIteratorMock.key(any()))
.then(newAnswerInt("key1", 0x00));
when(rocksIteratorMock.value(any()))
.then(newAnswerInt("val1", 0x7f));
byte[] key = null;
byte[] value = null;
try (RDBStoreCodecBufferIterator i = newIterator()) {
if (i.hasNext()) {
Table.KeyValue<CodecBuffer, CodecBuffer> entry = i.next();
key = entry.getKey().getArray();
value = entry.getValue().getArray();
}
}
InOrder verifier = inOrder(rocksIteratorMock);
verifier.verify(rocksIteratorMock, times(1)).isValid();
verifier.verify(rocksIteratorMock, times(1)).key(any());
assertArrayEquals(new byte[]{0x00}, key);
assertArrayEquals(new byte[]{0x7f}, value);
CodecTestUtil.gc();
}
@Test
public void testRemovingFromDBActuallyDeletesFromTable() throws Exception {
final byte[] testKey = new byte[10];
ThreadLocalRandom.current().nextBytes(testKey);
when(rocksIteratorMock.isValid()).thenReturn(true);
when(rocksIteratorMock.key(any()))
.then(newAnswer("key1", testKey));
try (RDBStoreCodecBufferIterator i = newIterator(null)) {
i.removeFromDB();
}
InOrder verifier = inOrder(rocksIteratorMock, rdbTableMock);
verifier.verify(rocksIteratorMock, times(1)).isValid();
verifier.verify(rdbTableMock, times(1))
.delete(ByteBuffer.wrap(testKey));
CodecTestUtil.gc();
}
@Test
public void testRemoveFromDBWithoutDBTableSet() throws Exception {
try (RDBStoreCodecBufferIterator i = newIterator()) {
assertThrows(UnsupportedOperationException.class,
i::removeFromDB);
}
CodecTestUtil.gc();
}
@Test
public void testCloseCloses() throws Exception {
newIterator().close();
verify(rocksIteratorMock, times(1)).close();
CodecTestUtil.gc();
}
@Test
public void testNullPrefixedIterator() throws Exception {
try (RDBStoreCodecBufferIterator i = newIterator()) {
verify(rocksIteratorMock, times(1)).seekToFirst();
clearInvocations(rocksIteratorMock);
i.seekToFirst();
verify(rocksIteratorMock, times(1)).seekToFirst();
clearInvocations(rocksIteratorMock);
when(rocksIteratorMock.isValid()).thenReturn(true);
assertTrue(i.hasNext());
verify(rocksIteratorMock, times(1)).isValid();
verify(rocksIteratorMock, times(0)).key(any());
i.seekToLast();
verify(rocksIteratorMock, times(1)).seekToLast();
}
CodecTestUtil.gc();
}
@Test
public void testNormalPrefixedIterator() throws Exception {
final byte[] prefixBytes = "sample".getBytes(StandardCharsets.UTF_8);
try (RDBStoreCodecBufferIterator i = newIterator(
CodecBuffer.wrap(prefixBytes))) {
final ByteBuffer prefix = ByteBuffer.wrap(prefixBytes);
verify(rocksIteratorMock, times(1)).seek(prefix);
clearInvocations(rocksIteratorMock);
i.seekToFirst();
verify(rocksIteratorMock, times(1)).seek(prefix);
clearInvocations(rocksIteratorMock);
when(rocksIteratorMock.isValid()).thenReturn(true);
when(rocksIteratorMock.key(any()))
.then(newAnswer("key1", prefixBytes));
assertTrue(i.hasNext());
verify(rocksIteratorMock, times(1)).isValid();
verify(rocksIteratorMock, times(1)).key(any());
Exception e =
assertThrows(Exception.class, () -> i.seekToLast(), "Prefixed iterator does not support seekToLast");
assertInstanceOf(UnsupportedOperationException.class, e);
}
CodecTestUtil.gc();
}
}