package com.netflix.discovery;
import javax.ws.rs.core.MediaType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.converters.EntityBodyConverter;
import com.netflix.discovery.junit.resource.DiscoveryClientResource;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.util.EurekaEntityFunctions;
import com.netflix.discovery.util.InstanceInfoGenerator;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockserver.client.server.MockServerClient;
import org.mockserver.junit.MockServerRule;
import org.mockserver.matchers.Times;
import org.mockserver.model.Header;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.mockserver.verify.VerificationTimes.atLeast;
import static org.mockserver.verify.VerificationTimes.exactly;
/**
* @author Tomasz Bak
*/
public class DiscoveryClientRedirectTest {
static class MockClientHolder {
MockServerClient client;
}
private final InstanceInfo myInstanceInfo = InstanceInfoGenerator.takeOne();
@Rule
public MockServerRule redirectServerMockRule = new MockServerRule(this);
private MockServerClient redirectServerMockClient;
private MockClientHolder targetServerMockClient = new MockClientHolder();
@Rule
public MockServerRule targetServerMockRule = new MockServerRule(targetServerMockClient);
@Rule
public DiscoveryClientResource registryFetchClientRule = DiscoveryClientResource.newBuilder()
.withRegistration(false)
.withRegistryFetch(true)
.withPortResolver(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return redirectServerMockRule.getHttpPort();
}
})
.withInstanceInfo(myInstanceInfo)
.build();
@Rule
public DiscoveryClientResource registeringClientRule = DiscoveryClientResource.newBuilder()
.withRegistration(true)
.withRegistryFetch(false)
.withPortResolver(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return redirectServerMockRule.getHttpPort();
}
})
.withInstanceInfo(myInstanceInfo)
.build();
private String targetServerBaseUri;
private final InstanceInfoGenerator dataGenerator = InstanceInfoGenerator.newBuilder(2, 1).withMetaData(true).build();
@Before
public void setUp() throws Exception {
targetServerBaseUri = "http://localhost:" + targetServerMockRule.getHttpPort();
}
@After
public void tearDown() {
if (redirectServerMockClient != null) {
redirectServerMockClient.reset();
}
if (targetServerMockClient.client != null) {
targetServerMockClient.client.reset();
}
}
@Test
public void testClientQueryFollowsRedirectsAndPinsToTargetServer() throws Exception {
Applications fullFetchApps = dataGenerator.takeDelta(1);
String fullFetchJson = toJson(fullFetchApps);
Applications deltaFetchApps = dataGenerator.takeDelta(1);
String deltaFetchJson = toJson(deltaFetchApps);
redirectServerMockClient.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/")
).respond(
response()
.withStatusCode(302)
.withHeader(new Header("Location", targetServerBaseUri + "/eureka/v2/apps/"))
);
targetServerMockClient.client.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/")
).respond(
response()
.withStatusCode(200)
.withHeader(new Header("Content-Type", "application/json"))
.withBody(fullFetchJson)
);
targetServerMockClient.client.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/delta")
).respond(
response()
.withStatusCode(200)
.withHeader(new Header("Content-Type", "application/json"))
.withBody(deltaFetchJson)
);
final EurekaClient client = registryFetchClientRule.getClient();
await(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
List<Application> applicationList = client.getApplications().getRegisteredApplications();
return !applicationList.isEmpty() && applicationList.get(0).getInstances().size() == 2;
}
}, 1, TimeUnit.MINUTES);
redirectServerMockClient.verify(request().withMethod("GET").withPath("/eureka/v2/apps/"), exactly(1));
redirectServerMockClient.verify(request().withMethod("GET").withPath("/eureka/v2/apps/delta"), exactly(0));
targetServerMockClient.client.verify(request().withMethod("GET").withPath("/eureka/v2/apps/"), exactly(1));
targetServerMockClient.client.verify(request().withMethod("GET").withPath("/eureka/v2/apps/delta"), atLeast(1));
}
// There is an issue with using mock-server for this test case. For now it is verified manually that it works.
@Ignore
@Test
public void testClientRegistrationFollowsRedirectsAndPinsToTargetServer() throws Exception {
}
@Test
public void testClientFallsBackToOriginalServerOnError() throws Exception {
Applications fullFetchApps1 = dataGenerator.takeDelta(1);
String fullFetchJson1 = toJson(fullFetchApps1);
Applications fullFetchApps2 = EurekaEntityFunctions.mergeApplications(fullFetchApps1, dataGenerator.takeDelta(1));
String fullFetchJson2 = toJson(fullFetchApps2);
redirectServerMockClient.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/")
).respond(
response()
.withStatusCode(302)
.withHeader(new Header("Location", targetServerBaseUri + "/eureka/v2/apps/"))
);
targetServerMockClient.client.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/"),
Times.exactly(1)
).respond(
response()
.withStatusCode(200)
.withHeader(new Header("Content-Type", "application/json"))
.withBody(fullFetchJson1)
);
targetServerMockClient.client.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/delta"),
Times.exactly(1)
).respond(
response()
.withStatusCode(500)
);
redirectServerMockClient.when(
request()
.withMethod("GET")
.withPath("/eureka/v2/apps/delta")
).respond(
response()
.withStatusCode(200)
.withHeader(new Header("Content-Type", "application/json"))
.withBody(fullFetchJson2)
);
final EurekaClient client = registryFetchClientRule.getClient();
await(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
List<Application> applicationList = client.getApplications().getRegisteredApplications();
return !applicationList.isEmpty() && applicationList.get(0).getInstances().size() == 2;
}
}, 1, TimeUnit.MINUTES);
redirectServerMockClient.verify(request().withMethod("GET").withPath("/eureka/v2/apps/"), exactly(1));
redirectServerMockClient.verify(request().withMethod("GET").withPath("/eureka/v2/apps/delta"), exactly(1));
targetServerMockClient.client.verify(request().withMethod("GET").withPath("/eureka/v2/apps/"), exactly(1));
targetServerMockClient.client.verify(request().withMethod("GET").withPath("/eureka/v2/apps/delta"), exactly(1));
}
private static String toJson(Applications applications) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
new EntityBodyConverter().write(applications, os, MediaType.APPLICATION_JSON_TYPE);
os.close();
return os.toString();
}
private static void await(Callable<Boolean> condition, long time, TimeUnit timeUnit) throws Exception {
long timeout = System.currentTimeMillis() + timeUnit.toMillis(time);
while (!condition.call()) {
if (System.currentTimeMillis() >= timeout) {
throw new TimeoutException();
}
Thread.sleep(100);
}
}
}