/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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
*
* 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.kaaproject.kaa.server.appenders.flume.appender.client.async;
import org.apache.avro.AvroRemoteException;
import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.Responder;
import org.apache.avro.ipc.Server;
import org.apache.avro.ipc.specific.SpecificResponder;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.FlumeException;
import org.apache.flume.api.RpcClientConfigurationConstants;
import org.apache.flume.event.EventBuilder;
import org.apache.flume.source.avro.AvroFlumeEvent;
import org.apache.flume.source.avro.AvroSourceProtocol;
import org.apache.flume.source.avro.Status;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.compression.ZlibDecoder;
import org.jboss.netty.handler.codec.compression.ZlibEncoder;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class AvroAsyncRpcClientTest {
private static final Logger LOG = LoggerFactory
.getLogger(AvroAsyncRpcClientTest.class);
private static final String localhost = "localhost";
public static void handlerBatchAppendAsyncTest(AvroSourceProtocol handler, int batchSize, int numberOfAsyncThreads, int numberOfBatchs, boolean enableServerCompression, boolean enableClientCompression, int compressionLevel)
throws FlumeException, EventDeliveryException {
AvroAsyncRpcClient client = null;
Server server = startServer(handler, 0, enableServerCompression);
try {
Properties starterProp = new Properties();
if (enableClientCompression) {
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_TYPE, "deflate");
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_LEVEL, "" + compressionLevel);
} else {
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_TYPE, "none");
}
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_BATCH_SIZE, Integer.toString(batchSize));
starterProp.setProperty(AvroAsyncRpcClient.ASYNC_MAX_THREADS, Integer.toString(numberOfAsyncThreads));
client = getStockLocalClient(server.getPort(), starterProp);
boolean isActive = client.isActive();
Assert.assertTrue("Client should be active", isActive);
ArrayList<Future<AppendBatchAsyncResultPojo>> futureList = new ArrayList<Future<AppendBatchAsyncResultPojo>>();
for (int b = 0; b < numberOfBatchs; b++) {
List<Event> events = new ArrayList<Event>();
for (int i = 0; i < batchSize; i++) {
events.add(EventBuilder.withBody(((char) b) + ": " + i, Charset.forName("UTF8")));
}
//long startTime = System.currentTimeMillis();
futureList.add(client.appendBatchAsync(events));
//System.out.println(" - Send Batch 1:" + (System.currentTimeMillis() - startTime));
}
for (int b = 0; b < numberOfBatchs; b++) {
AppendBatchAsyncResultPojo pojo = futureList.get(b).get();
Assert.assertTrue(pojo.isSuccessful);
Assert.assertTrue(pojo.getEvents().size() == batchSize);
Assert.assertTrue((pojo.getEvents().get(0).getBody()[0]) == b);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
stopServer(server);
if (client != null) {
client.close();
}
}
}
public static void handlerAppendAsyncTest(AvroSourceProtocol handler, int numberOfAsyncThreads, int numberOfEvent, boolean enableServerCompression, boolean enableClientCompression, int compressionLevel)
throws FlumeException, EventDeliveryException {
AvroAsyncRpcClient client = null;
Server server = startServer(handler, 0, enableServerCompression);
try {
Properties starterProp = new Properties();
if (enableClientCompression) {
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_TYPE, "deflate");
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_LEVEL, "" + compressionLevel);
} else {
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_COMPRESSION_TYPE, "none");
}
starterProp.setProperty(AvroAsyncRpcClient.ASYNC_MAX_THREADS, Integer.toString(numberOfAsyncThreads));
client = getStockLocalClient(server.getPort(), starterProp);
boolean isActive = client.isActive();
Assert.assertTrue("Client should be active", isActive);
ArrayList<Future<AppendAsyncResultPojo>> futureList = new ArrayList<Future<AppendAsyncResultPojo>>();
for (int b = 0; b < numberOfEvent; b++) {
futureList.add(client.appendAsync(EventBuilder.withBody(b + ": 1", Charset.forName("UTF8"))));
}
for (int b = 0; b < numberOfEvent; b++) {
AppendAsyncResultPojo pojo = futureList.get(b).get();
Assert.assertTrue(pojo.isSuccessful);
Assert.assertTrue(Character.getNumericValue(((char) (pojo.getEvent().getBody()[0]))) == b);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
stopServer(server);
if (client != null) {
client.close();
}
}
}
/**
* Start a NettyServer, wait a moment for it to spin up, and return it.
*/
public static Server startServer(AvroSourceProtocol handler, int port, boolean enableCompression) {
Responder responder = new SpecificResponder(AvroSourceProtocol.class,
handler);
Server server;
if (enableCompression) {
server = new NettyServer(responder,
new InetSocketAddress(localhost, port),
new NioServerSocketChannelFactory
(Executors.newFixedThreadPool(10), Executors.newFixedThreadPool(10)),
new CompressionChannelPipelineFactory(), null);
} else {
server = new NettyServer(responder,
new InetSocketAddress(localhost, port));
}
server.start();
LOG.info("Server started on hostname: {}, port: {}",
new Object[]{localhost, Integer.toString(server.getPort())});
try {
Thread.sleep(300L);
} catch (InterruptedException ex) {
LOG.error("Thread interrupted. Exception follows.", ex);
Thread.currentThread().interrupt();
}
return server;
}
public static void stopServer(Server server) {
try {
server.close();
server.join();
} catch (InterruptedException ex) {
LOG.error("Thread interrupted. Exception follows.", ex);
Thread.currentThread().interrupt();
}
}
public static AvroAsyncRpcClient getStockLocalClient(int port) {
Properties props = new Properties();
return getStockLocalClient(port, props);
}
public static AvroAsyncRpcClient getStockLocalClient(int port, Properties starterProp) {
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_HOSTS, "h1");
starterProp.setProperty(RpcClientConfigurationConstants.CONFIG_HOSTS_PREFIX + "h1", "127.0.0.1" + ":" + port);
AvroAsyncRpcClient client = new AvroAsyncRpcClient(starterProp, 2);
return client;
}
@Test
public void testOkAppendBatchAsyncClient() throws FlumeException, EventDeliveryException {
handlerBatchAppendAsyncTest(new OKAvroHandler(), 10, 5, 5, false, false, 0);
handlerBatchAppendAsyncTest(new OKAvroHandler(), 10, 5, 10, false, false, 0);
handlerBatchAppendAsyncTest(new OKAvroHandler(), 10, 5, 10, true, true, 6);
}
@Test
public void testOkSleepAppendBatchAsyncClient() throws FlumeException, EventDeliveryException {
//This will send 100,000 messages and wait 50 milliseconds for each batch
//1 thread will take over 5 seconds
//10 threads will take under a second
long startTime = System.currentTimeMillis();
handlerBatchAppendAsyncTest(new OKSleepAvroHandler(50), 1000, 1, 100, false, false, 0);
System.out.println("Test 1: " + (System.currentTimeMillis() - startTime));
startTime = System.currentTimeMillis();
handlerBatchAppendAsyncTest(new OKSleepAvroHandler(50), 1000, 5, 100, false, false, 0);
System.out.println("Test 2: " + (System.currentTimeMillis() - startTime));
startTime = System.currentTimeMillis();
handlerBatchAppendAsyncTest(new OKSleepAvroHandler(50), 1000, 10, 100, false, false, 0);
System.out.println("Test 3: " + (System.currentTimeMillis() - startTime));
}
@Test
public void testOkAppendAsyncClient() throws FlumeException, EventDeliveryException {
handlerAppendAsyncTest(new OKAvroHandler(), 5, 5, false, false, 0);
handlerAppendAsyncTest(new OKAvroHandler(), 5, 10, false, false, 0);
handlerAppendAsyncTest(new OKAvroHandler(), 5, 10, true, true, 6);
}
private static class CompressionChannelPipelineFactory implements
ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
ZlibEncoder encoder = new ZlibEncoder(6);
pipeline.addFirst("deflater", encoder);
pipeline.addFirst("inflater", new ZlibDecoder());
return pipeline;
}
}
/**
* A service that logs receipt of the request and returns Failed
*/
public static class FailedAvroHandler implements AvroSourceProtocol {
@Override
public Status append(AvroFlumeEvent event) throws AvroRemoteException {
LOG.info("Failed: Received event from append(): {}",
new String(event.getBody().array(), Charset.forName("UTF8")));
return Status.FAILED;
}
@Override
public Status appendBatch(List<AvroFlumeEvent> events) throws
AvroRemoteException {
LOG.info("Failed: Received {} events from appendBatch()",
events.size());
return Status.FAILED;
}
}
/**
* A service that logs receipt of the request and returns Unknown
*/
public static class UnknownAvroHandler implements AvroSourceProtocol {
@Override
public Status append(AvroFlumeEvent event) throws AvroRemoteException {
LOG.info("Unknown: Received event from append(): {}",
new String(event.getBody().array(), Charset.forName("UTF8")));
return Status.UNKNOWN;
}
@Override
public Status appendBatch(List<AvroFlumeEvent> events) throws
AvroRemoteException {
LOG.info("Unknown: Received {} events from appendBatch()",
events.size());
return Status.UNKNOWN;
}
}
/**
* A service that logs receipt of the request and returns OK
*/
public static class OKAvroHandler implements AvroSourceProtocol {
@Override
public Status append(AvroFlumeEvent event) throws AvroRemoteException {
LOG.info("OK: Received event from append(): {}",
new String(event.getBody().array(), Charset.forName("UTF8")));
return Status.OK;
}
@Override
public Status appendBatch(List<AvroFlumeEvent> events) throws
AvroRemoteException {
LOG.info("OK: Received {} events from appendBatch()",
events.size());
return Status.OK;
}
}
/**
* A service that logs receipt of the request and returns OK
*/
public static class OKSleepAvroHandler implements AvroSourceProtocol {
int sleepTime;
public OKSleepAvroHandler(int sleepTime) {
this.sleepTime = sleepTime;
}
@Override
public Status append(AvroFlumeEvent event) throws AvroRemoteException {
LOG.info("OK Sleep: Received event from append(): {}",
new String(event.getBody().array(), Charset.forName("UTF8")));
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return Status.OK;
}
@Override
public Status appendBatch(List<AvroFlumeEvent> events) throws
AvroRemoteException {
//System.out.println("OK Sleep: Received {} events from appendBatch()" + events.size());
LOG.info("OK Sleep: Received {} events from appendBatch()",
events.size());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return Status.OK;
}
}
}