/** * 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 com.twitter.distributedlog.client; import com.google.common.collect.Lists; import com.twitter.distributedlog.DLSN; import com.twitter.distributedlog.LogRecord; import com.twitter.distributedlog.LogRecordSet; import com.twitter.distributedlog.LogRecordSetBuffer; import com.twitter.distributedlog.exceptions.LogRecordTooLongException; import com.twitter.distributedlog.io.CompressionCodec; import com.twitter.distributedlog.service.DistributedLogClient; import com.twitter.finagle.IndividualRequestTimeoutException; import com.twitter.util.Await; import com.twitter.util.Future; import com.twitter.util.Promise; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.nio.ByteBuffer; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static com.google.common.base.Charsets.UTF_8; import static org.mockito.Mockito.*; import static org.junit.Assert.*; /** * Test {@link DistributedLogMultiStreamWriter} */ public class TestDistributedLogMultiStreamWriter { @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithNullStreams() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .build(); } @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithEmptyStreamList() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.<String>newArrayList()) .build(); } @Test(timeout = 20000, expected = NullPointerException.class) public void testBuildWithNullClient() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .build(); } @Test(timeout = 20000, expected = NullPointerException.class) public void testBuildWithNullCodec() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(null) .build(); } @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithInvalidSpeculativeSettings1() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(-1) .build(); } @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithInvalidSpeculativeSettings2() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(5) .build(); } @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithInvalidSpeculativeSettings3() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(-1) .build(); } @Test(timeout = 20000, expected = IllegalArgumentException.class) public void testBuildWithInvalidSpeculativeSettings4() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(10) .build(); } @Test(timeout = 20000) public void testBuildMultiStreamWriter() throws Exception { DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(50) .build(); assertTrue(true); } @Test(timeout = 20000) public void testBuildWithPeriodicalFlushEnabled() throws Exception { ScheduledExecutorService executorService = mock(ScheduledExecutorService.class); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(50) .flushIntervalMs(1000) .scheduler(executorService) .build(); verify(executorService, times(1)).scheduleAtFixedRate(writer, 1000000, 1000000, TimeUnit.MICROSECONDS); } @Test(timeout = 20000) public void testBuildWithPeriodicalFlushDisabled() throws Exception { ScheduledExecutorService executorService = mock(ScheduledExecutorService.class); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(mock(DistributedLogClient.class)) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(50) .flushIntervalMs(0) .scheduler(executorService) .build(); verify(executorService, times(0)).scheduleAtFixedRate(writer, 1000, 1000, TimeUnit.MILLISECONDS); writer.close(); } @Test(timeout = 20000) public void testFlushWhenBufferIsFull() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); when(client.writeRecordSet((String) any(), (LogRecordSetBuffer) any())) .thenReturn(Future.value(new DLSN(1L, 1L, 999L))); ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(100000) .maxSpeculativeTimeoutMs(200000) .speculativeBackoffMultiplier(2) .requestTimeoutMs(500000) .flushIntervalMs(0) .bufferSize(0) .scheduler(executorService) .build(); ByteBuffer buffer = ByteBuffer.wrap("test".getBytes(UTF_8)); writer.write(buffer); verify(client, times(1)).writeRecordSet((String) any(), (LogRecordSetBuffer) any()); writer.close(); } @Test(timeout = 20000) public void testFlushWhenExceedMaxLogRecordSetSize() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); when(client.writeRecordSet((String) any(), (LogRecordSetBuffer) any())) .thenReturn(Future.value(new DLSN(1L, 1L, 999L))); ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(100000) .maxSpeculativeTimeoutMs(200000) .speculativeBackoffMultiplier(2) .requestTimeoutMs(500000) .flushIntervalMs(0) .bufferSize(Integer.MAX_VALUE) .scheduler(executorService) .build(); byte[] data = new byte[LogRecord.MAX_LOGRECORD_SIZE - 3 * 100]; ByteBuffer buffer1 = ByteBuffer.wrap(data); writer.write(buffer1); verify(client, times(0)).writeRecordSet((String) any(), (LogRecordSetBuffer) any()); LogRecordSet.Writer recordSetWriter1 = writer.getLogRecordSetWriter(); assertEquals(1, recordSetWriter1.getNumRecords()); assertEquals(LogRecordSet.HEADER_LEN + 4 + data.length, recordSetWriter1.getNumBytes()); ByteBuffer buffer2 = ByteBuffer.wrap(data); writer.write(buffer2); verify(client, times(1)).writeRecordSet((String) any(), (LogRecordSetBuffer) any()); LogRecordSet.Writer recordSetWriter2 = writer.getLogRecordSetWriter(); assertEquals(1, recordSetWriter2.getNumRecords()); assertEquals(LogRecordSet.HEADER_LEN + 4 + data.length, recordSetWriter2.getNumBytes()); assertTrue(recordSetWriter1 != recordSetWriter2); writer.close(); } @Test(timeout = 20000) public void testWriteTooLargeRecord() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(100000) .maxSpeculativeTimeoutMs(200000) .speculativeBackoffMultiplier(2) .requestTimeoutMs(5000000) .flushIntervalMs(0) .bufferSize(0) .build(); byte[] data = new byte[LogRecord.MAX_LOGRECORD_SIZE + 10]; ByteBuffer buffer = ByteBuffer.wrap(data); Future<DLSN> writeFuture = writer.write(buffer); assertTrue(writeFuture.isDefined()); try { Await.result(writeFuture); fail("Should fail on writing too long record"); } catch (LogRecordTooLongException lrtle) { // expected } writer.close(); } @Test(timeout = 20000) public void testSpeculativeWrite() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(5000000) .flushIntervalMs(0) .bufferSize(0) .build(); final String secondStream = writer.getStream(1); final DLSN dlsn = new DLSN(99L, 88L, 0L); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String stream = (String) arguments[0]; if (stream.equals(secondStream)) { return Future.value(dlsn); } else { return new Promise<DLSN>(); } } }).when(client).writeRecordSet((String) any(), (LogRecordSetBuffer) any()); byte[] data = "test-test".getBytes(UTF_8); ByteBuffer buffer = ByteBuffer.wrap(data); Future<DLSN> writeFuture = writer.write(buffer); DLSN writeDLSN = Await.result(writeFuture); assertEquals(dlsn, writeDLSN); writer.close(); } @Test(timeout = 20000) public void testPeriodicalFlush() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(5000000) .flushIntervalMs(10) .bufferSize(Integer.MAX_VALUE) .build(); final DLSN dlsn = new DLSN(99L, 88L, 0L); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return Future.value(dlsn); } }).when(client).writeRecordSet((String) any(), (LogRecordSetBuffer) any()); byte[] data = "test-test".getBytes(UTF_8); ByteBuffer buffer = ByteBuffer.wrap(data); Future<DLSN> writeFuture = writer.write(buffer); DLSN writeDLSN = Await.result(writeFuture); assertEquals(dlsn, writeDLSN); writer.close(); } @Test(timeout = 20000) public void testFailRequestAfterRetriedAllStreams() throws Exception { DistributedLogClient client = mock(DistributedLogClient.class); when(client.writeRecordSet((String) any(), (LogRecordSetBuffer) any())) .thenReturn(new Promise<DLSN>()); DistributedLogMultiStreamWriter writer = DistributedLogMultiStreamWriter.newBuilder() .streams(Lists.newArrayList("stream1", "stream2")) .client(client) .compressionCodec(CompressionCodec.Type.LZ4) .firstSpeculativeTimeoutMs(10) .maxSpeculativeTimeoutMs(20) .speculativeBackoffMultiplier(2) .requestTimeoutMs(5000000) .flushIntervalMs(10) .bufferSize(Integer.MAX_VALUE) .build(); byte[] data = "test-test".getBytes(UTF_8); ByteBuffer buffer = ByteBuffer.wrap(data); Future<DLSN> writeFuture = writer.write(buffer); try { Await.result(writeFuture); fail("Should fail the request after retries all streams"); } catch (IndividualRequestTimeoutException e) { long timeoutMs = e.timeout().inMilliseconds(); assertTrue(timeoutMs >= (10 + 20) && timeoutMs < 5000000); } writer.close(); } }