package com.netflix.eureka.resources; import java.io.File; import java.io.FilenameFilter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.discovery.shared.resolver.DefaultEndpoint; import com.netflix.discovery.shared.transport.EurekaHttpClient; import com.netflix.discovery.shared.transport.EurekaHttpResponse; import com.netflix.discovery.shared.transport.TransportClientFactory; import com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory; import com.netflix.discovery.util.InstanceInfoGenerator; import com.netflix.eureka.EurekaServerConfig; import com.netflix.eureka.cluster.protocol.ReplicationInstance; import com.netflix.eureka.cluster.protocol.ReplicationInstanceResponse; import com.netflix.eureka.cluster.protocol.ReplicationList; import com.netflix.eureka.cluster.protocol.ReplicationListResponse; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.Action; import com.netflix.eureka.transport.JerseyReplicationClient; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test REST layer of client/server communication. This test instantiates fully configured Jersey container, * which is essential to verifying content encoding/decoding with different format types (JSON vs XML, compressed vs * uncompressed). * * @author Tomasz Bak */ public class EurekaClientServerRestIntegrationTest { private static final String[] EUREKA1_WAR_DIRS = {"build/libs", "eureka-server/build/libs"}; private static final Pattern WAR_PATTERN = Pattern.compile("eureka-server.*.war"); private static EurekaServerConfig eurekaServerConfig; private static Server server; private static TransportClientFactory httpClientFactory; private static EurekaHttpClient jerseyEurekaClient; private static JerseyReplicationClient jerseyReplicationClient; /** * We do not include ASG data to prevent server from consulting AWS for its status. */ private static final InstanceInfoGenerator infoGenerator = InstanceInfoGenerator.newBuilder(10, 2).withAsg(false).build(); private static final Iterator<InstanceInfo> instanceInfoIt = infoGenerator.serviceIterator(); private static String eurekaServiceUrl; @BeforeClass public static void setUp() throws Exception { injectEurekaConfiguration(); startServer(); createEurekaServerConfig(); httpClientFactory = JerseyEurekaHttpClientFactory.newBuilder() .withClientName("testEurekaClient") .withConnectionTimeout(1000) .withReadTimeout(1000) .withMaxConnectionsPerHost(1) .withMaxTotalConnections(1) .withConnectionIdleTimeout(1000) .build(); jerseyEurekaClient = httpClientFactory.newClient(new DefaultEndpoint(eurekaServiceUrl)); ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig); jerseyReplicationClient = JerseyReplicationClient.createReplicationClient( eurekaServerConfig, serverCodecs, eurekaServiceUrl ); } @AfterClass public static void tearDown() throws Exception { removeEurekaConfiguration(); if (jerseyReplicationClient != null) { jerseyReplicationClient.shutdown(); } if (server != null) { server.stop(); } if (httpClientFactory != null) { httpClientFactory.shutdown(); } } @Test public void testRegistration() throws Exception { InstanceInfo instanceInfo = instanceInfoIt.next(); EurekaHttpResponse<Void> httpResponse = jerseyEurekaClient.register(instanceInfo); assertThat(httpResponse.getStatusCode(), is(equalTo(204))); } @Test public void testHeartbeat() throws Exception { // Register first InstanceInfo instanceInfo = instanceInfoIt.next(); jerseyEurekaClient.register(instanceInfo); // Now send heartbeat EurekaHttpResponse<InstanceInfo> heartBeatResponse = jerseyReplicationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); assertThat(heartBeatResponse.getStatusCode(), is(equalTo(200))); assertThat(heartBeatResponse.getEntity(), is(nullValue())); } @Test public void testMissedHeartbeat() throws Exception { InstanceInfo instanceInfo = instanceInfoIt.next(); // Now send heartbeat EurekaHttpResponse<InstanceInfo> heartBeatResponse = jerseyReplicationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); assertThat(heartBeatResponse.getStatusCode(), is(equalTo(404))); } @Test public void testCancelForEntryThatExists() throws Exception { // Register first InstanceInfo instanceInfo = instanceInfoIt.next(); jerseyEurekaClient.register(instanceInfo); // Now cancel EurekaHttpResponse<Void> httpResponse = jerseyEurekaClient.cancel(instanceInfo.getAppName(), instanceInfo.getId()); assertThat(httpResponse.getStatusCode(), is(equalTo(200))); } @Test public void testCancelForEntryThatDoesNotExist() throws Exception { // Now cancel InstanceInfo instanceInfo = instanceInfoIt.next(); EurekaHttpResponse<Void> httpResponse = jerseyEurekaClient.cancel(instanceInfo.getAppName(), instanceInfo.getId()); assertThat(httpResponse.getStatusCode(), is(equalTo(404))); } @Test public void testStatusOverrideUpdateAndDelete() throws Exception { // Register first InstanceInfo instanceInfo = instanceInfoIt.next(); jerseyEurekaClient.register(instanceInfo); // Now override status EurekaHttpResponse<Void> overrideUpdateResponse = jerseyEurekaClient.statusUpdate(instanceInfo.getAppName(), instanceInfo.getId(), InstanceStatus.DOWN, instanceInfo); assertThat(overrideUpdateResponse.getStatusCode(), is(equalTo(200))); InstanceInfo fetchedInstance = expectInstanceInfoInRegistry(instanceInfo); assertThat(fetchedInstance.getStatus(), is(equalTo(InstanceStatus.DOWN))); // Now remove override EurekaHttpResponse<Void> deleteOverrideResponse = jerseyEurekaClient.deleteStatusOverride(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo); assertThat(deleteOverrideResponse.getStatusCode(), is(equalTo(200))); fetchedInstance = expectInstanceInfoInRegistry(instanceInfo); assertThat(fetchedInstance.getStatus(), is(equalTo(InstanceStatus.UNKNOWN))); } @Test public void testBatch() throws Exception { InstanceInfo instanceInfo = instanceInfoIt.next(); ReplicationInstance replicationInstance = ReplicationInstance.replicationInstance() .withAction(Action.Register) .withAppName(instanceInfo.getAppName()) .withId(instanceInfo.getId()) .withInstanceInfo(instanceInfo) .withLastDirtyTimestamp(System.currentTimeMillis()) .withStatus(instanceInfo.getStatus().name()) .build(); EurekaHttpResponse<ReplicationListResponse> httpResponse = jerseyReplicationClient.submitBatchUpdates(new ReplicationList(replicationInstance)); assertThat(httpResponse.getStatusCode(), is(equalTo(200))); List<ReplicationInstanceResponse> replicationListResponse = httpResponse.getEntity().getResponseList(); assertThat(replicationListResponse.size(), is(equalTo(1))); assertThat(replicationListResponse.get(0).getStatusCode(), is(equalTo(200))); } private static InstanceInfo expectInstanceInfoInRegistry(InstanceInfo instanceInfo) { EurekaHttpResponse<InstanceInfo> queryResponse = jerseyEurekaClient.getInstance(instanceInfo.getAppName(), instanceInfo.getId()); assertThat(queryResponse.getStatusCode(), is(equalTo(200))); assertThat(queryResponse.getEntity(), is(notNullValue())); assertThat(queryResponse.getEntity().getId(), is(equalTo(instanceInfo.getId()))); return queryResponse.getEntity(); } /** * This will be read by server internal discovery client. We need to salience it. */ private static void injectEurekaConfiguration() throws UnknownHostException { String myHostName = InetAddress.getLocalHost().getHostName(); String myServiceUrl = "http://" + myHostName + ":8080/v2/"; System.setProperty("eureka.region", "default"); System.setProperty("eureka.name", "eureka"); System.setProperty("eureka.vipAddress", "eureka.mydomain.net"); System.setProperty("eureka.port", "8080"); System.setProperty("eureka.preferSameZone", "false"); System.setProperty("eureka.shouldUseDns", "false"); System.setProperty("eureka.shouldFetchRegistry", "false"); System.setProperty("eureka.serviceUrl.defaultZone", myServiceUrl); System.setProperty("eureka.serviceUrl.default.defaultZone", myServiceUrl); System.setProperty("eureka.awsAccessId", "fake_aws_access_id"); System.setProperty("eureka.awsSecretKey", "fake_aws_secret_key"); System.setProperty("eureka.numberRegistrySyncRetries", "0"); } private static void removeEurekaConfiguration() { } private static void startServer() throws Exception { File warFile = findWar(); server = new Server(8080); WebAppContext webapp = new WebAppContext(); webapp.setContextPath("/"); webapp.setWar(warFile.getAbsolutePath()); server.setHandler(webapp); server.start(); eurekaServiceUrl = "http://localhost:8080/v2"; } private static File findWar() { File dir = null; for (String candidate : EUREKA1_WAR_DIRS) { File candidateFile = new File(candidate); if (candidateFile.exists()) { dir = candidateFile; break; } } if (dir == null) { throw new IllegalStateException("No directory found at any in any pre-configured location: " + Arrays.toString(EUREKA1_WAR_DIRS)); } File[] warFiles = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return WAR_PATTERN.matcher(name).matches(); } }); if (warFiles.length == 0) { throw new IllegalStateException("War file not found in directory " + dir); } if (warFiles.length > 1) { throw new IllegalStateException("Multiple war files found in directory " + dir + ": " + Arrays.toString(warFiles)); } return warFiles[0]; } private static void createEurekaServerConfig() { eurekaServerConfig = mock(EurekaServerConfig.class); // Cluster management related when(eurekaServerConfig.getPeerEurekaNodesUpdateIntervalMs()).thenReturn(1000); // Replication logic related when(eurekaServerConfig.shouldSyncWhenTimestampDiffers()).thenReturn(true); when(eurekaServerConfig.getMaxTimeForReplication()).thenReturn(1000); when(eurekaServerConfig.getMaxElementsInPeerReplicationPool()).thenReturn(10); when(eurekaServerConfig.getMinThreadsForPeerReplication()).thenReturn(1); when(eurekaServerConfig.getMaxThreadsForPeerReplication()).thenReturn(1); when(eurekaServerConfig.shouldBatchReplication()).thenReturn(true); // Peer node connectivity (used by JerseyReplicationClient) when(eurekaServerConfig.getPeerNodeTotalConnections()).thenReturn(1); when(eurekaServerConfig.getPeerNodeTotalConnectionsPerHost()).thenReturn(1); when(eurekaServerConfig.getPeerNodeConnectionIdleTimeoutSeconds()).thenReturn(1000); } }