/** * Licensed 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 */ package org.corfudb.runtime.view.replication; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.assertj.core.api.Assertions; import org.corfudb.infrastructure.LogUnitServer; import org.corfudb.infrastructure.LogUnitServerAssertions; import org.corfudb.infrastructure.SequencerServer; import org.corfudb.infrastructure.ServerContextBuilder; import org.corfudb.infrastructure.TestLayoutBuilder; import org.corfudb.infrastructure.TestServerRouter; import org.corfudb.protocols.wireprotocol.CorfuMsg; import org.corfudb.protocols.wireprotocol.CorfuMsgType; import org.corfudb.protocols.wireprotocol.CorfuPayloadMsg; import org.corfudb.protocols.wireprotocol.DataType; import org.corfudb.protocols.wireprotocol.ILogData; import org.corfudb.protocols.wireprotocol.IMetadata; import org.corfudb.protocols.wireprotocol.LogData; import org.corfudb.protocols.wireprotocol.TokenRequest; import org.corfudb.protocols.wireprotocol.TokenResponse; import org.corfudb.protocols.wireprotocol.WriteMode; import org.corfudb.protocols.wireprotocol.WriteRequest; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.runtime.view.AbstractViewTest; import org.corfudb.runtime.view.Address; import org.corfudb.runtime.view.Layout; import org.corfudb.runtime.view.stream.IStreamView; import org.corfudb.util.serializer.Serializers; import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; /** * Created by Konstantin Spirov on 1/30/2017. */ public class QuorumReplicationProtocolAdditionalTests extends AbstractViewTest { public static final UUID testClientId = UUID.nameUUIDFromBytes("TEST_CLIENT".getBytes()); private Layout layout = null; private CorfuRuntime corfuRuntime = null; @Before public void before() { addServer(SERVERS.PORT_0); addServer(SERVERS.PORT_1); addServer(SERVERS.PORT_2); layout = new TestLayoutBuilder() .setEpoch(1L) .addLayoutServer(SERVERS.PORT_0) .addLayoutServer(SERVERS.PORT_1) .addLayoutServer(SERVERS.PORT_2) .addSequencer(SERVERS.PORT_0) .buildSegment() .setReplicationMode(Layout.ReplicationMode.QUORUM_REPLICATION) .buildStripe() .addLogUnit(SERVERS.PORT_0) .addLogUnit(SERVERS.PORT_1) .addLogUnit(SERVERS.PORT_2) .addToSegment() .addToLayout() .build(); bootstrapAllServers(layout); getManagementServer(SERVERS.PORT_0).shutdown(); getManagementServer(SERVERS.PORT_1).shutdown(); getManagementServer(SERVERS.PORT_2).shutdown(); layout.getSegment(0L).setReplicationMode(Layout.ReplicationMode.QUORUM_REPLICATION); corfuRuntime = new CorfuRuntime(); corfuRuntime.setCacheDisabled(true); layout.getLayoutServers().forEach(corfuRuntime::addLayoutServer); layout.getAllServers().forEach(serverEndpoint -> { corfuRuntime.getRouter(serverEndpoint).setTimeoutConnect(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis()); corfuRuntime.getRouter(serverEndpoint).setTimeoutResponse(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis()); corfuRuntime.getRouter(serverEndpoint).setTimeoutRetry(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis()); }); corfuRuntime.connect(); } public CorfuRuntime getDefaultRuntime() { return corfuRuntime; } @Test @SuppressWarnings("unchecked") public void checkRecoveryWriteTriggeredFromReadRecoversDataWhenTheQuorumIsLost() throws Exception { //configure the layout accordingly CorfuRuntime r = getDefaultRuntime(); LogUnitServer u0 = getLogUnit(SERVERS.PORT_0); LogUnitServer u1 = getLogUnit(SERVERS.PORT_1); LogUnitServer u2 = getLogUnit(SERVERS.PORT_2); final long ADDRESS_0 = 0L; //write at 0 ByteBuf b = Unpooled.buffer(); Serializers.CORFU.serialize("0".getBytes(), b); WriteRequest m = WriteRequest.builder() .writeMode(WriteMode.NORMAL) .data(new LogData(DataType.DATA, b)) .build(); m.setGlobalAddress(ADDRESS_0); m.setRank(new IMetadata.DataRank(0)); m.setBackpointerMap(Collections.emptyMap()); sendMessage(u1, CorfuMsgType.WRITE.payloadMsg(m)); sendMessage(u2, CorfuMsgType.WRITE.payloadMsg(m)); u2.setShutdown(true); u2.shutdown(); LogUnitServerAssertions.assertThat(u0) .isEmptyAtAddress(ADDRESS_0); assertThat(r.getAddressSpaceView().read(0L).getPayload(getRuntime())) .isEqualTo("0".getBytes()); LogUnitServerAssertions.assertThat(u1) .matchesDataAtAddress(ADDRESS_0, "0".getBytes()); LogUnitServerAssertions.assertThat(u0) .matchesDataAtAddress(ADDRESS_0, "0".getBytes()); } @Test @SuppressWarnings("unchecked") public void checkReadOnEmptyPosition() throws Exception { //configure the layout accordingly CorfuRuntime r = getDefaultRuntime(); LogUnitServer u0 = getLogUnit(SERVERS.PORT_0); UUID streamA = CorfuRuntime.getStreamID("stream A"); byte[] testPayload = "hello world".getBytes(); //generate a stream hole TokenResponse tr = r.getSequencerView().nextToken(Collections.singleton(streamA), 1); IStreamView sv = r.getStreamsView().get(streamA); sv.append(testPayload); tr = r.getSequencerView().nextToken(Collections.singleton(streamA), 1); //make sure we can still read the stream. assertThat(sv.next().getPayload(getRuntime())) .isEqualTo(testPayload); int address = 0; assertThat(r.getAddressSpaceView().read(address++).getType()).isEqualTo(DataType.HOLE); assertThat(r.getAddressSpaceView().read(address++).getType()).isEqualTo(DataType.DATA); assertThat(r.getAddressSpaceView().read(address++).getType()).isEqualTo(DataType.HOLE); // TODO(mwei) - fix me // assertThat(r.getAddressSpaceView().read(address++).getType()).isEqualTo(DataType.EMPTY); } @Test @SuppressWarnings("unchecked") public void canReadWrite() throws Exception { CorfuRuntime r = getDefaultRuntime(); UUID streamA = UUID.nameUUIDFromBytes("stream A".getBytes()); byte[] testPayload = "hello world".getBytes(); r.getAddressSpaceView().write(new TokenResponse(0, r.getLayoutView().getLayout().getEpoch(), Collections.singletonMap(streamA, Address.NO_BACKPOINTER)), testPayload); ILogData x = r.getAddressSpaceView().read(0); assertNotNull(x.getRank()); assertThat(r.getAddressSpaceView().read(0L).getPayload(r)) .isEqualTo("hello world".getBytes()); assertThat(r.getAddressSpaceView().read(0L).containsStream(streamA)) .isTrue(); assertThat((IMetadata.DataRank) r.getAddressSpaceView().read(0L).getMetadataMap() .get(IMetadata.LogUnitMetadataType.RANK)).isNotNull(); } @Test @SuppressWarnings("unchecked") public void canReadWriteConcurrent() throws Exception { CorfuRuntime r = getDefaultRuntime(); final int numberThreads = 5; final int numberRecords = 1_000; scheduleConcurrently(numberThreads, threadNumber -> { int base = threadNumber * numberRecords; for (int i = base; i < base + numberRecords; i++) { r.getAddressSpaceView().write(new TokenResponse((long)i, r.getLayoutView().getLayout().getEpoch(), Collections.singletonMap(CorfuRuntime.getStreamID("a"), Address.NO_BACKPOINTER)), Integer.toString(i).getBytes()); } }); executeScheduled(numberThreads, PARAMETERS.TIMEOUT_LONG); scheduleConcurrently(numberThreads, threadNumber -> { int base = threadNumber * numberRecords; for (int i = base; i < base + numberRecords; i++) { assertThat(r.getAddressSpaceView().read(i).getPayload(getRuntime())) .isEqualTo(Integer.toString(i).getBytes()); } }); executeScheduled(numberThreads, PARAMETERS.TIMEOUT_LONG); assertNotNull(r.getAddressSpaceView().read(0L).getRank()); assertThat((IMetadata.DataRank) r.getAddressSpaceView().read(0L).getMetadataMap() .get(IMetadata.LogUnitMetadataType.RANK)).isNotNull(); } @Test @SuppressWarnings("unchecked") public void canReadWriteToMultiple() throws Exception { //configure the layout accordingly CorfuRuntime r = getDefaultRuntime(); UUID streamA = UUID.nameUUIDFromBytes("stream A".getBytes()); byte[] testPayload = "hello world".getBytes(); r.getAddressSpaceView().write(new TokenResponse(0, r.getLayoutView().getLayout().getEpoch(), Collections.singletonMap(streamA, Address.NO_BACKPOINTER)), testPayload); assertThat(r.getAddressSpaceView().read(0L).getPayload(getRuntime())) .isEqualTo("hello world".getBytes()); assertThat(r.getAddressSpaceView().read(0L) .containsStream(streamA)).isTrue(); assertThat((IMetadata.DataRank) r.getAddressSpaceView().read(0L).getMetadataMap() .get(IMetadata.LogUnitMetadataType.RANK)).isNotNull(); } @Test @SuppressWarnings("unchecked") public void ensureAllUnitsContainData() throws Exception { //configure the layout accordingly CorfuRuntime r = getDefaultRuntime(); UUID streamA = UUID.nameUUIDFromBytes("stream A".getBytes()); byte[] testPayload = "hello world".getBytes(); r.getAddressSpaceView().write(new TokenResponse(0, 1, Collections.singletonMap(streamA, Address.NO_BACKPOINTER)), testPayload); assertThat(r.getAddressSpaceView().read(0L).getPayload(getRuntime())) .isEqualTo("hello world".getBytes()); assertThat(r.getAddressSpaceView().read(0L).containsStream(streamA)); // Ensure that the data was written to each logunit. LogUnitServerAssertions.assertThat(getLogUnit(SERVERS.PORT_0)) .matchesDataAtAddress(0, testPayload); LogUnitServerAssertions.assertThat(getLogUnit(SERVERS.PORT_1)) .matchesDataAtAddress(0, testPayload); LogUnitServerAssertions.assertThat(getLogUnit(SERVERS.PORT_2)) .matchesDataAtAddress(0, testPayload); } public void sendMessage(LogUnitServer s, CorfuMsg message) { TestServerRouter router = new TestServerRouter(); router.addServer(s); message.setClientID(testClientId); message.setRequestID(requestCounter.getAndIncrement()); router.sendServerMessage(message); } private AtomicInteger requestCounter = new AtomicInteger(0); }