/*
Copyright (c) 2017 LinkedIn Corp.
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 com.linkedin.r2.disruptor;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import com.linkedin.r2.filter.NextFilter;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestRequest;
import com.linkedin.r2.message.rest.RestRequestBuilder;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.message.stream.StreamRequest;
import com.linkedin.r2.message.stream.StreamRequestBuilder;
import com.linkedin.r2.message.stream.StreamResponse;
import com.linkedin.r2.message.stream.entitystream.EntityStreams;
import org.easymock.EasyMock;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
/**
* @author Sean Sheng
* @version $Revision$
*/
public class TestDisruptFilter
{
private static final String DISRUPT_CONTEXT_KEY = "R2_DISRUPT_CONTEXT";
private static final int SCHEDULER_THREADS = 1;
private static final int EXECUTOR_THREADS = 1;
private static final int TEST_TIMEOUT = 5000;
private static final String URI = "http://foo.com/";
private static final int REQUEST_TIMEOUT = 0;
private static final long REQUEST_LATENCY = 0;
private final ScheduledExecutorService _scheduler = new ScheduledThreadPoolExecutor(SCHEDULER_THREADS);
private final ExecutorService _executor = Executors.newFixedThreadPool(EXECUTOR_THREADS);
@AfterClass
public void doAfterClass()
{
_scheduler.shutdown();
_executor.shutdown();
}
@Test
public void testRestLatencyDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.delay(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<RestRequest, RestResponse> next = new NextFilter<RestRequest, RestResponse>()
{
@Override
public void onRequest(RestRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(true);
latch.countDown();
}
@Override
public void onResponse(RestResponse restResponse, RequestContext requestContext,
Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
};
filter.onRestRequest(new RestRequestBuilder(new URI(URI)).build(), requestContext, Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testStreamLatencyDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.delay(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<StreamRequest, StreamResponse> next = new NextFilter<StreamRequest, StreamResponse>()
{
@Override
public void onRequest(StreamRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(true);
latch.countDown();
}
@Override
public void onResponse(StreamResponse restResponse, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
};
filter.onStreamRequest(new StreamRequestBuilder(new URI(URI)).build(EntityStreams.emptyStream()), requestContext,
Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testRestTimeoutDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.timeout());
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<RestRequest, RestResponse> next = new NextFilter<RestRequest, RestResponse>()
{
@Override
public void onRequest(RestRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onResponse(RestResponse restResponse, RequestContext requestContext,
Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(ex instanceof TimeoutException);
latch.countDown();
}
};
filter.onRestRequest(new RestRequestBuilder(new URI(URI)).build(), requestContext, Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testStreamTimeoutDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.timeout());
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<StreamRequest, StreamResponse> next = new NextFilter<StreamRequest, StreamResponse>()
{
@Override
public void onRequest(StreamRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onResponse(StreamResponse restResponse, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(ex instanceof TimeoutException);
latch.countDown();
}
};
filter.onStreamRequest(new StreamRequestBuilder(new URI(URI)).build(EntityStreams.emptyStream()), requestContext,
Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testRestErrorDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.error(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<RestRequest, RestResponse> next = new NextFilter<RestRequest, RestResponse>()
{
@Override
public void onRequest(RestRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onResponse(RestResponse restResponse, RequestContext requestContext,
Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(ex instanceof DisruptedException);
latch.countDown();
}
};
filter.onRestRequest(new RestRequestBuilder(new URI(URI)).build(), requestContext, Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testStreamErrorDisrupt() throws Exception
{
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.error(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(_scheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<StreamRequest, StreamResponse> next = new NextFilter<StreamRequest, StreamResponse>()
{
@Override
public void onRequest(StreamRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onResponse(StreamResponse restResponse, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(ex instanceof DisruptedException);
latch.countDown();
}
};
filter.onStreamRequest(new StreamRequestBuilder(new URI(URI)).build(EntityStreams.emptyStream()), requestContext,
Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
}
@Test
public void testSchedulerRejectExecution() throws Exception
{
ScheduledExecutorService rejectedScheduler = EasyMock.createStrictMock(ScheduledExecutorService.class);
EasyMock.expect(rejectedScheduler.schedule(
EasyMock.anyObject(Runnable.class),
EasyMock.anyLong(),
EasyMock.anyObject(TimeUnit.class))).andThrow(new RejectedExecutionException());
EasyMock.replay(rejectedScheduler);
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.error(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(rejectedScheduler, _executor, REQUEST_TIMEOUT);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
final NextFilter<StreamRequest, StreamResponse> next = new NextFilter<StreamRequest, StreamResponse>()
{
@Override
public void onRequest(StreamRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(true);
latch.countDown();
}
@Override
public void onResponse(StreamResponse restResponse, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
latch.countDown();
}
};
filter.onStreamRequest(new StreamRequestBuilder(
new URI(URI)).build(EntityStreams.emptyStream()),
requestContext,
Collections.emptyMap(),
next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
EasyMock.verify(rejectedScheduler);
EasyMock.reset(rejectedScheduler);
}
@Test
public void testExecutorRejectExecution() throws Exception
{
final AtomicBoolean success = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(1);
ExecutorService rejectedExecutor = EasyMock.createStrictMock(ExecutorService.class);
rejectedExecutor.execute(EasyMock.anyObject(Runnable.class));
EasyMock.expectLastCall().andAnswer(() -> {
success.set(true);
latch.countDown();
throw new RejectedExecutionException();
});
EasyMock.replay(rejectedExecutor);
final RequestContext requestContext = new RequestContext();
requestContext.putLocalAttr(DISRUPT_CONTEXT_KEY, DisruptContexts.error(REQUEST_LATENCY));
final DisruptFilter filter = new DisruptFilter(_scheduler, rejectedExecutor, REQUEST_TIMEOUT);
final NextFilter<StreamRequest, StreamResponse> next = new NextFilter<StreamRequest, StreamResponse>()
{
@Override
public void onRequest(StreamRequest restRequest, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(false);
latch.countDown();
}
@Override
public void onResponse(StreamResponse restResponse, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(false);
latch.countDown();
}
@Override
public void onError(Throwable ex, RequestContext requestContext, Map<String, String> wireAttrs)
{
success.set(false);
latch.countDown();
}
};
filter.onStreamRequest(new StreamRequestBuilder(
new URI(URI)).build(EntityStreams.emptyStream()),
requestContext,
Collections.emptyMap(), next);
Assert.assertTrue(latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS), "Missing NextFilter invocation");
Assert.assertTrue(success.get(), "Unexpected method invocation");
EasyMock.verify(rejectedExecutor);
EasyMock.reset(rejectedExecutor);
}
}