/**
* Copyright 2016 Netflix, 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 com.netflix.hystrix.contrib.metrics.controller;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.junit.Test;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import com.netflix.hystrix.contrib.metrics.HystrixStream;
import com.netflix.hystrix.contrib.metrics.HystrixStreamingOutputProvider;
public class StreamingOutputProviderTest {
private final Observable<String> streamOfOnNexts = Observable.interval(100, TimeUnit.MILLISECONDS).map(new Func1<Long, String>() {
@Override
public String call(Long timestamp) {
return "test-stream";
}
});
private final Observable<String> streamOfOnNextThenOnError = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Thread.sleep(100);
subscriber.onNext("test-stream");
Thread.sleep(100);
subscriber.onError(new RuntimeException("stream failure"));
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}).subscribeOn(Schedulers.computation());
private final Observable<String> streamOfOnNextThenOnCompleted = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
Thread.sleep(100);
subscriber.onNext("test-stream");
Thread.sleep(100);
subscriber.onCompleted();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}).subscribeOn(Schedulers.computation());
private AbstractHystrixStreamController sse = new AbstractHystrixStreamController(streamOfOnNexts) {
private final AtomicInteger concurrentConnections = new AtomicInteger(0);
@Override
protected int getMaxNumberConcurrentConnectionsAllowed() {
return 2;
}
@Override
protected AtomicInteger getCurrentConnections() {
return concurrentConnections;
}
};
@Test
public void concurrencyTest() throws Exception {
Response resp = sse.handleRequest();
assertEquals(200, resp.getStatus());
assertEquals("text/event-stream;charset=UTF-8", resp.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
assertEquals("no-cache, no-store, max-age=0, must-revalidate", resp.getHeaders().getFirst(HttpHeaders.CACHE_CONTROL));
assertEquals("no-cache", resp.getHeaders().getFirst("Pragma"));
resp = sse.handleRequest();
assertEquals(200, resp.getStatus());
resp = sse.handleRequest();
assertEquals(503, resp.getStatus());
assertEquals("MaxConcurrentConnections reached: " + sse.getMaxNumberConcurrentConnectionsAllowed(), resp.getEntity());
sse.getCurrentConnections().decrementAndGet();
resp = sse.handleRequest();
assertEquals(200, resp.getStatus());
}
@Test
public void testInfiniteOnNextStream() throws Exception {
final PipedInputStream is = new PipedInputStream();
final PipedOutputStream os = new PipedOutputStream(is);
final AtomicInteger writes = new AtomicInteger(0);
final HystrixStream stream = new HystrixStream(streamOfOnNexts, 100, new AtomicInteger(1));
Thread streamingThread = startStreamingThread(stream, os);
verifyStream(is, writes);
Thread.sleep(1000); // Let the provider stream for some time.
streamingThread.interrupt(); // Stop streaming
os.close();
is.close();
System.out.println("Total lines:" + writes.get());
assertTrue(writes.get() >= 9); // Observable is configured to emit events in every 100 ms. So expect at least 9 in a second.
assertTrue(stream.getConcurrentConnections().get() == 0); // Provider is expected to decrement connection count when streaming process is terminated.
}
@Test
public void testOnError() throws Exception {
testStreamOnce(streamOfOnNextThenOnError);
}
@Test
public void testOnComplete() throws Exception {
testStreamOnce(streamOfOnNextThenOnCompleted);
}
private void testStreamOnce(Observable<String> observable) throws Exception {
final PipedInputStream is = new PipedInputStream();
final PipedOutputStream os = new PipedOutputStream(is);
final AtomicInteger writes = new AtomicInteger(0);
final HystrixStream stream = new HystrixStream(observable, 100, new AtomicInteger(1));
startStreamingThread(stream, os);
verifyStream(is, writes);
Thread.sleep(1000);
os.close();
is.close();
System.out.println("Total lines:" + writes.get());
assertTrue(writes.get() == 1);
assertTrue(stream.getConcurrentConnections().get() == 0);
}
private static Thread startStreamingThread(final HystrixStream stream, final OutputStream outputSteam) {
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
try {
final HystrixStreamingOutputProvider provider = new HystrixStreamingOutputProvider();
provider.writeTo(stream, null, null, null, null, null, outputSteam);
} catch (IOException e) {
fail(e.getMessage());
}
}
});
th1.start();
return th1;
}
private static void verifyStream(final InputStream is, final AtomicInteger lineCount) {
Thread th2 = new Thread(new Runnable() {
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
if (!"".equals(line)) {
System.out.println(line);
lineCount.incrementAndGet();
}
}
} catch (IOException e) {
fail("Failed while verifying streaming output.Stacktrace:" + e.getMessage());
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
fail("Failed while verifying streaming output.Stacktrace:" + e.getMessage());
}
}
}
}
});
th2.start();
}
}