/**
* 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 javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.ServiceUnavailableException;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.apache.commons.configuration.SystemConfiguration;
import org.glassfish.jersey.media.sse.EventInput;
import org.glassfish.jersey.media.sse.InboundEvent;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.junit.Assert;
import org.junit.Test;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.contrib.metrics.HystrixStreamFeature;
/**
* @author justinjose28
*
*/
@Path("/hystrix")
public class HystricsMetricsControllerTest extends JerseyTest {
protected static final AtomicInteger requestCount = new AtomicInteger(0);
@POST
@Path("/command")
@Consumes(APPLICATION_JSON)
public void command() throws Exception {
TestHystrixCommand command = new TestHystrixCommand();
command.execute();
}
@Override
protected Application configure() {
int port = 0;
try {
final ServerSocket socket = new ServerSocket(0);
port = socket.getLocalPort();
socket.close();
} catch (IOException e1) {
throw new RuntimeException("Failed to find port to start test server");
}
set(TestProperties.CONTAINER_PORT, port);
try {
SystemConfiguration.setSystemProperties("test.properties");
} catch (Exception e) {
throw new RuntimeException("Failed to load config file");
}
return new ResourceConfig(HystricsMetricsControllerTest.class, HystrixStreamFeature.class);
}
protected String getPath() {
return "hystrix.stream";
}
protected boolean isStreamValid(String data) {
return data.contains("\"type\":\"HystrixThreadPool\"") && data.contains("\"currentCompletedTaskCount\":" + requestCount.get());
}
@Test
public void testInfiniteStream() throws Exception {
executeHystrixCommand(); // Execute a Hystrix command so that metrics are initialized.
EventInput stream = getStream(); // Invoke Stream API which returns a steady stream output.
validateStream(stream, 1000); // Validate the stream.
System.out.println("Validated Stream Output 1");
executeHystrixCommand(); // Execute Hystrix Command again so that request count is updated.
validateStream(stream, 1000); // Stream should show updated request count
System.out.println("Validated Stream Output 2");
stream.close();
}
@Test
public void testConcurrency() throws Exception {
executeHystrixCommand(); // Execute a Hystrix command so that metrics are initialized.
List<EventInput> streamList = new ArrayList<EventInput>();
// Fire 5 requests, validate their responses and hold these connections.
for (int i = 0; i < 5; i++) {
EventInput stream = getStream();
System.out.println("Received Response for Request#" + (i + 1));
streamList.add(stream);
validateStream(stream, 1000);
System.out.println("Validated Response#" + (i + 1));
}
// Sixth request should fail since max configured connection is 5.
try {
streamList.add(getStreamFailFast());
Assert.fail("Expected 'ServiceUnavailableException' but, request went through.");
} catch (ServiceUnavailableException e) {
System.out.println("Got ServiceUnavailableException as expected.");
}
// Close one of the connections
streamList.get(0).close();
// Try again after closing one of the connections. This request should go through.
EventInput eventInput = getStream();
streamList.add(eventInput);
validateStream(eventInput, 1000);
}
private void executeHystrixCommand() throws Exception {
Response response = target("hystrix/command").request().post(null);
assertEquals(204, response.getStatus());
System.out.println("Hystrix Command ran successfully.");
requestCount.incrementAndGet();
}
private EventInput getStream() throws Exception {
long timeElapsed = System.currentTimeMillis();
while (System.currentTimeMillis() - timeElapsed < 3000) {
try {
return getStreamFailFast();
} catch (Exception e) {
}
}
fail("Not able to connect to Stream end point");
return null;
}
private EventInput getStreamFailFast() throws Exception {
return target(getPath()).request().get(EventInput.class);
}
private void validateStream(EventInput eventInput, long waitTime) {
long timeElapsed = System.currentTimeMillis();
while (!eventInput.isClosed() && System.currentTimeMillis() - timeElapsed < waitTime) {
final InboundEvent inboundEvent = eventInput.read();
if (inboundEvent == null) {
Assert.fail("Failed while verifying stream. Looks like connection has been closed.");
break;
}
String data = inboundEvent.readData(String.class);
System.out.println(data);
if (isStreamValid(data)) {
return;
}
}
Assert.fail("Failed while verifying stream");
}
public static class TestHystrixCommand extends HystrixCommand<Void> {
protected TestHystrixCommand() {
super(HystrixCommandGroupKey.Factory.asKey("test"));
}
@Override
protected Void run() throws Exception {
return null;
}
}
}