package org.corfudb.runtime.clients;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.corfudb.format.Types;
import org.corfudb.infrastructure.AbstractServer;
import org.corfudb.infrastructure.LogUnitServer;
import org.corfudb.infrastructure.ServerContext;
import org.corfudb.infrastructure.ServerContextBuilder;
import org.corfudb.infrastructure.log.StreamLogFiles;
import org.corfudb.protocols.wireprotocol.DataType;
import org.corfudb.protocols.wireprotocol.ILogData;
import org.corfudb.protocols.wireprotocol.IMetadata;
import org.corfudb.protocols.wireprotocol.IToken;
import org.corfudb.protocols.wireprotocol.LogData;
import org.corfudb.protocols.wireprotocol.ReadResponse;
import org.corfudb.runtime.CorfuRuntime;
import org.corfudb.runtime.exceptions.DataCorruptionException;
import org.corfudb.runtime.exceptions.DataOutrankedException;
import org.corfudb.runtime.exceptions.OverwriteException;
import org.corfudb.runtime.exceptions.ValueAdoptedException;
import org.junit.Test;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.corfudb.infrastructure.log.StreamLogFiles.METADATA_SIZE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* Created by mwei on 12/14/15.
*/
public class LogUnitClientTest extends AbstractClientTest {
LogUnitClient client;
ServerContext serverContext;
String dirPath;
LogUnitServer server;
@Override
Set<AbstractServer> getServersForTest() {
dirPath = PARAMETERS.TEST_TEMP_DIR;
serverContext = new ServerContextBuilder()
.setInitialToken(0)
.setSingle(false)
.setNoVerify(false)
.setMemory(false)
.setLogPath(dirPath)
.setServerRouter(serverRouter)
.build();
server = new LogUnitServer(serverContext);
return new ImmutableSet.Builder<AbstractServer>()
.add(server)
.build();
}
@Override
Set<IClient> getClientsForTest() {
client = new LogUnitClient();
return new ImmutableSet.Builder<IClient>()
.add(new BaseClient())
.add(client)
.build();
}
@Test
public void canReadWrite()
throws Exception {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), null, testString, Collections.emptyMap()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString);
}
@Test
public void canReadWriteRanked()
throws Exception {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), new IMetadata.DataRank(1), testString, Collections.emptyMap()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString);
byte[] testString2 = "hello world 2".getBytes();
client.write(0, Collections.<UUID>emptySet(), new IMetadata.DataRank(2), testString2, Collections.emptyMap()).get();
r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString2);
}
@Test
public void cannotOutrank() throws ExecutionException, InterruptedException {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), new IMetadata.DataRank(2), testString, Collections.emptyMap()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString);
byte[] testString2 = "hello world 2".getBytes();
try {
client.write(0, Collections.<UUID>emptySet(), new IMetadata.DataRank(1), testString2, Collections.emptyMap()).get();
fail();
} catch (ExecutionException e) {
// expected
assertEquals(DataOutrankedException.class, e.getCause().getClass());
}
r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString);
}
@Test
public void valueCanBeAdopted() throws ExecutionException, InterruptedException {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), new IMetadata.DataRank(1), testString, Collections.emptyMap()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType()) .isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime()))
.isEqualTo(testString);
try {
ILogData data = createEmptyData(0, DataType.RANK_ONLY, new IMetadata.DataRank(2)).getSerialized();
client.write(data).get();
fail();
} catch (Exception e) {
// expected
assertEquals(ValueAdoptedException.class, e.getCause().getClass());
ValueAdoptedException ex = (ValueAdoptedException)e.getCause();
ReadResponse read = ex.getReadResponse();
LogData log = read.getReadSet().get(0l);
assertThat(log.getType()).isEqualTo(DataType.DATA);
assertThat(log.getPayload(new CorfuRuntime())).isEqualTo(testString);;
}
r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType()).isEqualTo(DataType.DATA);
assertThat(r.getPayload(new CorfuRuntime())).isEqualTo(testString);
}
private ILogData.SerializationHandle createEmptyData(long position, DataType type, IMetadata.DataRank rank) {
ILogData data = new LogData(type);
data.setRank(rank);
data.setGlobalAddress(position);
return data.getSerializedForm();
}
@Test
public void overwriteThrowsException()
throws Exception {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), null, testString, Collections.emptyMap()).get();
assertThatThrownBy(() -> client.write(0, Collections.<UUID>emptySet(), null,
testString, Collections.emptyMap()).get())
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(OverwriteException.class);
}
@Test
public void holeFillCannotBeOverwritten()
throws Exception {
byte[] testString = "hello world".getBytes();
client.fillHole(0).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.HOLE);
assertThatThrownBy(() -> client.write(0, Collections.<UUID>emptySet(), null, testString, Collections.emptyMap()).get())
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(OverwriteException.class);
}
@Test
public void holeFillCannotOverwrite()
throws Exception {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), null, testString, Collections.emptyMap()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getType())
.isEqualTo(DataType.DATA);
assertThatThrownBy(() -> client.fillHole(0).get())
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(OverwriteException.class);
}
@Test
public void backpointersCanBeWrittenAndRead()
throws Exception {
final long ADDRESS_0 = 1337L;
final long ADDRESS_1 = 1338L;
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), null, testString,
ImmutableMap.<UUID, Long>builder()
.put(CorfuRuntime.getStreamID("hello"), ADDRESS_0)
.put(CorfuRuntime.getStreamID("hello2"), ADDRESS_1)
.build()).get();
LogData r = client.read(0).get().getReadSet().get(0L);
assertThat(r.getBackpointerMap())
.containsEntry(CorfuRuntime.getStreamID("hello"), ADDRESS_0);
assertThat(r.getBackpointerMap())
.containsEntry(CorfuRuntime.getStreamID("hello2"), ADDRESS_1);
}
@Test
public void CorruptedDataReadThrowsException() throws Exception {
byte[] testString = "hello world".getBytes();
client.write(0, Collections.<UUID>emptySet(), null, testString, Collections.emptyMap()).get();
client.write(StreamLogFiles.RECORDS_PER_LOG_FILE + 1, Collections.<UUID>emptySet(), null,
testString, Collections.emptyMap()).get();
// Corrupt the written log entry
String logDir = serverContext.getServerConfig().get("--log-path") + File.separator + "log";
String logFilePath = logDir + File.separator + "0.log";
RandomAccessFile file = new RandomAccessFile(logFilePath, "rw");
ByteBuffer metaDataBuf = ByteBuffer.allocate(METADATA_SIZE);
file.getChannel().read(metaDataBuf);
metaDataBuf.flip();
Types.Metadata metadata = Types.Metadata.parseFrom(metaDataBuf.array());
final int fileOffset = Integer.BYTES + METADATA_SIZE + metadata.getLength() + 20;
final int CORRUPT_BYTES = 0xFFFF;
file.seek(fileOffset); // File header + delimiter
file.writeInt(CORRUPT_BYTES);
file.close();
LogUnitServer server2 = new LogUnitServer(serverContext);
serverRouter.reset();
serverRouter.addServer(server2);
// Try to read a corrupted log entry
assertThatThrownBy(() -> client.read(0).get())
.isInstanceOf(RuntimeException.class)
.hasCauseInstanceOf(DataCorruptionException.class);
}
}