diff --git a/eureka-client-jersey2/build.gradle b/eureka-client-jersey2/build.gradle index f4f3375b7..1fa6a46cd 100644 --- a/eureka-client-jersey2/build.gradle +++ b/eureka-client-jersey2/build.gradle @@ -24,7 +24,8 @@ configurations.all { dependencies { compile project(':eureka-client') - compile "org.glassfish.jersey.core:jersey-client:2.8" + compile 'org.glassfish.jersey.core:jersey-client:2.23.1' + compile 'org.glassfish.jersey.connectors:jersey-apache-connector:2.23.1' compile "javax.mail:mail:1.4.7" testCompile project(':eureka-test-utils') diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/Jersey2DiscoveryClientOptionalArgs.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/Jersey2DiscoveryClientOptionalArgs.java new file mode 100644 index 000000000..91e65c430 --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/Jersey2DiscoveryClientOptionalArgs.java @@ -0,0 +1,10 @@ +package com.netflix.discovery; + +import javax.ws.rs.client.ClientRequestFilter; + +/** + * Jersey2 implementation of DiscoveryClientOptionalArgs that supports supplying {@link ClientRequestFilter} + */ +public class Jersey2DiscoveryClientOptionalArgs extends AbstractDiscoveryClientOptionalArgs { + +} diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/AbstractJersey2EurekaHttpClient.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/AbstractJersey2EurekaHttpClient.java new file mode 100644 index 000000000..517911128 --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/AbstractJersey2EurekaHttpClient.java @@ -0,0 +1,349 @@ +/* + * Copyright 2015 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.discovery.shared.transport.jersey2; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; +import com.netflix.discovery.shared.transport.EurekaHttpClient; +import com.netflix.discovery.shared.transport.EurekaHttpResponse; +import com.netflix.discovery.shared.transport.EurekaHttpResponse.EurekaHttpResponseBuilder; +import com.netflix.discovery.util.StringUtil; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse; + +/** + * @author Tomasz Bak + */ +public abstract class AbstractJersey2EurekaHttpClient implements EurekaHttpClient { + + private static final Logger logger = LoggerFactory.getLogger(AbstractJersey2EurekaHttpClient.class); + + protected final EurekaJersey2Client jerseyClient; + protected final String serviceUrl; + private final boolean allowRedirect; + private final String userName; + private final String password; + + protected AbstractJersey2EurekaHttpClient(EurekaJersey2Client jerseyClient, String serviceUrl, boolean allowRedirect) { + this.jerseyClient = jerseyClient; + this.serviceUrl = serviceUrl; + this.allowRedirect = allowRedirect; + + // Jersey2 does not read credentials from the URI. We extract it here and enable authentication feature. + String localUserName = null; + String localPassword = null; + try { + URI serviceURI = new URI(serviceUrl); + if (serviceURI.getUserInfo() != null) { + String[] credentials = serviceURI.getUserInfo().split(":"); + if (credentials.length == 2) { + localUserName = credentials[0]; + localPassword = credentials[1]; + } + } + } catch (URISyntaxException ignore) { + } + this.userName = localUserName; + this.password = localPassword; + } + + @Override + public EurekaHttpResponse register(InstanceInfo info) { + String urlPath = "apps/" + info.getAppName(); + Response response = null; + try { + Builder resourceBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request(); + addExtraProperties(resourceBuilder); + addExtraHeaders(resourceBuilder); + response = resourceBuilder + .header(HttpHeaders.ACCEPT_ENCODING, "gzip") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_TYPE) + .accept(MediaType.APPLICATION_JSON) + .post(Entity.json(info)); + return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(), + response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse cancel(String appName, String id) { + String urlPath = "apps/" + appName + '/' + id; + Response response = null; + try { + Builder resourceBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request(); + addExtraProperties(resourceBuilder); + addExtraHeaders(resourceBuilder); + response = resourceBuilder.delete(); + return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) { + String urlPath = "apps/" + appName + '/' + id; + Response response = null; + try { + WebTarget webResource = jerseyClient.getClient().target(serviceUrl) + .path(urlPath) + .queryParam("status", info.getStatus().toString()) + .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()); + if (overriddenStatus != null) { + webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name()); + } + Builder requestBuilder = webResource.request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE)); // Jersey2 refuses to handle PUT with no body + EurekaHttpResponseBuilder eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response)); + if (response.hasEntity()) { + eurekaResponseBuilder.entity(response.readEntity(InstanceInfo.class)); + } + return eurekaResponseBuilder.build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info) { + String urlPath = "apps/" + appName + '/' + id + "/status"; + Response response = null; + try { + Builder requestBuilder = jerseyClient.getClient().target(serviceUrl) + .path(urlPath) + .queryParam("value", newStatus.name()) + .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()) + .request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.put(Entity.text("")); + return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse deleteStatusOverride(String appName, String id, InstanceInfo info) { + String urlPath = "apps/" + appName + '/' + id + "/status"; + Response response = null; + try { + Builder requestBuilder = jerseyClient.getClient().target(serviceUrl) + .path(urlPath) + .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()) + .request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.delete(); + return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse getApplications(String... regions) { + return getApplicationsInternal("apps/", regions); + } + + @Override + public EurekaHttpResponse getDelta(String... regions) { + return getApplicationsInternal("apps/delta", regions); + } + + @Override + public EurekaHttpResponse getVip(String vipAddress, String... regions) { + return getApplicationsInternal("vips/" + vipAddress, regions); + } + + @Override + public EurekaHttpResponse getSecureVip(String secureVipAddress, String... regions) { + return getApplicationsInternal("svips/" + secureVipAddress, regions); + } + + @Override + public EurekaHttpResponse getApplication(String appName) { + String urlPath = "apps/" + appName; + Response response = null; + try { + Builder requestBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(); + + Application application = null; + if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) { + application = response.readEntity(Application.class); + } + return anEurekaHttpResponse(response.getStatus(), application).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + private EurekaHttpResponse getApplicationsInternal(String urlPath, String[] regions) { + Response response = null; + String regionsParamValue = null; + try { + WebTarget webTarget = jerseyClient.getClient().target(serviceUrl).path(urlPath); + if (regions != null && regions.length > 0) { + webTarget = webTarget.queryParam("regions", StringUtil.join(regions)); + } + Builder requestBuilder = webTarget.request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(); + + Applications applications = null; + if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) { + applications = response.readEntity(Applications.class); + } + return anEurekaHttpResponse(response.getStatus(), applications).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse getInstance(String id) { + return getInstanceInternal("instances/" + id); + } + + @Override + public EurekaHttpResponse getInstance(String appName, String id) { + return getInstanceInternal("apps/" + appName + '/' + id); + } + + private EurekaHttpResponse getInstanceInternal(String urlPath) { + Response response = null; + try { + Builder requestBuilder = jerseyClient.getClient().target(serviceUrl).path(urlPath).request(); + addExtraProperties(requestBuilder); + addExtraHeaders(requestBuilder); + response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(); + + InstanceInfo infoFromPeer = null; + if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) { + infoFromPeer = response.readEntity(InstanceInfo.class); + } + return anEurekaHttpResponse(response.getStatus(), infoFromPeer).headers(headersOf(response)).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("Jersey2 HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public void shutdown() { + jerseyClient.destroyResources(); + } + + protected void addExtraProperties(Builder webResource) { + if (userName != null) { + webResource.property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, userName) + .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, password); + } + } + + protected void addExtraHeaders(Builder webResource) { + if (allowRedirect) { + webResource.header(DiscoveryClient.HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true"); + } + } + + private static Map headersOf(Response response) { + MultivaluedMap jerseyHeaders = response.getStringHeaders(); + if (jerseyHeaders == null || jerseyHeaders.isEmpty()) { + return Collections.emptyMap(); + } + Map headers = new HashMap<>(); + for (Entry> entry : jerseyHeaders.entrySet()) { + if (!entry.getValue().isEmpty()) { + headers.put(entry.getKey(), entry.getValue().get(0)); + } + } + return headers; + } +} diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaIdentityHeaderFilter.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaIdentityHeaderFilter.java new file mode 100644 index 000000000..278920b9a --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaIdentityHeaderFilter.java @@ -0,0 +1,30 @@ +package com.netflix.discovery.shared.transport.jersey2; + +import java.io.IOException; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; + +import com.netflix.appinfo.AbstractEurekaIdentity; + +public class EurekaIdentityHeaderFilter implements ClientRequestFilter { + + private final AbstractEurekaIdentity authInfo; + + public EurekaIdentityHeaderFilter(AbstractEurekaIdentity authInfo) { + this.authInfo = authInfo; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + if (authInfo != null) { + requestContext.getHeaders().putSingle(AbstractEurekaIdentity.AUTH_NAME_HEADER_KEY, authInfo.getName()); + requestContext.getHeaders().putSingle(AbstractEurekaIdentity.AUTH_VERSION_HEADER_KEY, authInfo.getVersion()); + + if (authInfo.getId() != null) { + requestContext.getHeaders().putSingle(AbstractEurekaIdentity.AUTH_ID_HEADER_KEY, authInfo.getId()); + } + } + + } +} diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2Client.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2Client.java new file mode 100644 index 000000000..bcdade104 --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2Client.java @@ -0,0 +1,16 @@ +package com.netflix.discovery.shared.transport.jersey2; + +import javax.ws.rs.client.Client; + +/** + * @author David Liu + */ +public interface EurekaJersey2Client { + + Client getClient(); + + /** + * Clean up resources. + */ + void destroyResources(); +} diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2ClientImpl.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2ClientImpl.java new file mode 100644 index 000000000..ed8e4bd57 --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/EurekaJersey2ClientImpl.java @@ -0,0 +1,354 @@ +package com.netflix.discovery.shared.transport.jersey2; + +import static com.netflix.discovery.util.DiscoveryBuildInfo.buildVersion; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; + +import org.apache.http.client.params.ClientPNames; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.params.CoreProtocolPNames; +import org.glassfish.jersey.apache.connector.ApacheClientProperties; +import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.discovery.converters.wrappers.CodecWrappers; +import com.netflix.discovery.converters.wrappers.DecoderWrapper; +import com.netflix.discovery.converters.wrappers.EncoderWrapper; +import com.netflix.discovery.provider.DiscoveryJerseyProvider; +import com.netflix.servo.monitor.BasicCounter; +import com.netflix.servo.monitor.BasicTimer; +import com.netflix.servo.monitor.Counter; +import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.servo.monitor.Monitors; +import com.netflix.servo.monitor.Stopwatch; + +/** + * @author Tomasz Bak + */ +public class EurekaJersey2ClientImpl implements EurekaJersey2Client { + + private static final Logger s_logger = LoggerFactory.getLogger(EurekaJersey2ClientImpl.class); + + private static final int HTTP_CONNECTION_CLEANER_INTERVAL_MS = 30 * 1000; + + private static final String PROTOCOL = "https"; + private static final String PROTOCOL_SCHEME = "SSL"; + private static final int HTTPS_PORT = 443; + private static final String KEYSTORE_TYPE = "JKS"; + + private final Client apacheHttpClient; + + ClientConfig jerseyClientConfig; + + private final ScheduledExecutorService eurekaConnCleaner = + Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + + private final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "Eureka-Jersey2Client-Conn-Cleaner" + threadNumber.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + }); + + public EurekaJersey2ClientImpl( + int connectionTimeout, + int readTimeout, + final int connectionIdleTimeout, + ClientConfig clientConfig) { + + try { + jerseyClientConfig = clientConfig; + jerseyClientConfig.register(DiscoveryJerseyProvider.class); + jerseyClientConfig.connectorProvider(new ApacheConnectorProvider()); + jerseyClientConfig.property(ClientProperties.CONNECT_TIMEOUT, connectionTimeout); + jerseyClientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout); + apacheHttpClient = ClientBuilder.newClient(jerseyClientConfig); + eurekaConnCleaner.scheduleWithFixedDelay( + new ConnectionCleanerTask(connectionIdleTimeout), HTTP_CONNECTION_CLEANER_INTERVAL_MS, + HTTP_CONNECTION_CLEANER_INTERVAL_MS, + TimeUnit.MILLISECONDS); + } catch (Throwable e) { + throw new RuntimeException("Cannot create Jersey2 client", e); + } + } + + @Override + public Client getClient() { + return apacheHttpClient; + } + + /** + * Clean up resources. + */ + @Override + public void destroyResources() { + if (eurekaConnCleaner != null) { + eurekaConnCleaner.shutdown(); + } + if (apacheHttpClient != null) { + apacheHttpClient.close(); + } + } + + public static class EurekaJersey2ClientBuilder { + + private boolean systemSSL; + private String clientName; + private int maxConnectionsPerHost; + private int maxTotalConnections; + private String trustStoreFileName; + private String trustStorePassword; + private String userAgent; + private String proxyUserName; + private String proxyPassword; + private String proxyHost; + private String proxyPort; + private int connectionTimeout; + private int readTimeout; + private int connectionIdleTimeout; + private EncoderWrapper encoderWrapper; + private DecoderWrapper decoderWrapper; + + public EurekaJersey2ClientBuilder withClientName(String clientName) { + this.clientName = clientName; + return this; + } + + public EurekaJersey2ClientBuilder withUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + + public EurekaJersey2ClientBuilder withConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + public EurekaJersey2ClientBuilder withReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + public EurekaJersey2ClientBuilder withConnectionIdleTimeout(int connectionIdleTimeout) { + this.connectionIdleTimeout = connectionIdleTimeout; + return this; + } + + public EurekaJersey2ClientBuilder withMaxConnectionsPerHost(int maxConnectionsPerHost) { + this.maxConnectionsPerHost = maxConnectionsPerHost; + return this; + } + + public EurekaJersey2ClientBuilder withMaxTotalConnections(int maxTotalConnections) { + this.maxTotalConnections = maxTotalConnections; + return this; + } + + public EurekaJersey2ClientBuilder withProxy(String proxyHost, String proxyPort, String user, String password) { + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.proxyUserName = user; + this.proxyPassword = password; + return this; + } + + public EurekaJersey2ClientBuilder withSystemSSLConfiguration() { + this.systemSSL = true; + return this; + } + + public EurekaJersey2ClientBuilder withTrustStoreFile(String trustStoreFileName, String trustStorePassword) { + this.trustStoreFileName = trustStoreFileName; + this.trustStorePassword = trustStorePassword; + return this; + } + + public EurekaJersey2ClientBuilder withEncoder(String encoderName) { + return this.withEncoderWrapper(CodecWrappers.getEncoder(encoderName)); + } + + public EurekaJersey2ClientBuilder withEncoderWrapper(EncoderWrapper encoderWrapper) { + this.encoderWrapper = encoderWrapper; + return this; + } + + public EurekaJersey2ClientBuilder withDecoder(String decoderName, String clientDataAccept) { + return this.withDecoderWrapper(CodecWrappers.resolveDecoder(decoderName, clientDataAccept)); + } + + public EurekaJersey2ClientBuilder withDecoderWrapper(DecoderWrapper decoderWrapper) { + this.decoderWrapper = decoderWrapper; + return this; + } + + public EurekaJersey2Client build() { + MyDefaultApacheHttpClient4Config config = new MyDefaultApacheHttpClient4Config(); + try { + return new EurekaJersey2ClientImpl( + connectionTimeout, + readTimeout, + connectionIdleTimeout, + config); + } catch (Throwable e) { + throw new RuntimeException("Cannot create Jersey client ", e); + } + } + + class MyDefaultApacheHttpClient4Config extends ClientConfig { + MyDefaultApacheHttpClient4Config() { + PoolingHttpClientConnectionManager cm; + + if (systemSSL) { + cm = createSystemSslCM(); + } else if (trustStoreFileName != null) { + cm = createCustomSslCM(); + } else { + cm = new PoolingHttpClientConnectionManager(); + } + + if (proxyHost != null) { + addProxyConfiguration(); + } + + DiscoveryJerseyProvider discoveryJerseyProvider = new DiscoveryJerseyProvider(encoderWrapper, decoderWrapper); +// getSingletons().add(discoveryJerseyProvider); + register(discoveryJerseyProvider); + + // Common properties to all clients + cm.setDefaultMaxPerRoute(maxConnectionsPerHost); + cm.setMaxTotal(maxTotalConnections); + property(ApacheClientProperties.CONNECTION_MANAGER, cm); + + String fullUserAgentName = (userAgent == null ? clientName : userAgent) + "/v" + buildVersion(); + property(CoreProtocolPNames.USER_AGENT, fullUserAgentName); + + // To pin a client to specific server in case redirect happens, we handle redirects directly + // (see DiscoveryClient.makeRemoteCall methods). + property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE); + property(ClientPNames.HANDLE_REDIRECTS, Boolean.FALSE); + + } + + private void addProxyConfiguration() { + if (proxyUserName != null && proxyPassword != null) { + property(ClientProperties.PROXY_USERNAME, proxyUserName); + property(ClientProperties.PROXY_PASSWORD, proxyPassword); + } else { + // Due to bug in apache client, user name/password must always be set. + // Otherwise proxy configuration is ignored. + property(ClientProperties.PROXY_USERNAME, "guest"); + property(ClientProperties.PROXY_PASSWORD, "guest"); + } + property(ClientProperties.PROXY_URI, "http://" + proxyHost + ":" + proxyPort); + } + + private PoolingHttpClientConnectionManager createSystemSslCM() { + ConnectionSocketFactory socketFactory = SSLConnectionSocketFactory.getSystemSocketFactory(); + + Registry registry = RegistryBuilder.create() + .register(PROTOCOL, socketFactory) + .build(); + + return new PoolingHttpClientConnectionManager(registry); + } + + private PoolingHttpClientConnectionManager createCustomSslCM() { + FileInputStream fin = null; + try { + SSLContext sslContext = SSLContext.getInstance(PROTOCOL_SCHEME); + KeyStore sslKeyStore = KeyStore.getInstance(KEYSTORE_TYPE); + + fin = new FileInputStream(trustStoreFileName); + sslKeyStore.load(fin, trustStorePassword.toCharArray()); + + TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init(sslKeyStore); + + TrustManager[] trustManagers = factory.getTrustManagers(); + + sslContext.init(null, trustManagers, null); + + ConnectionSocketFactory socketFactory = + new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + + Registry registry = RegistryBuilder.create() + .register(PROTOCOL, socketFactory) + .build(); + + return new PoolingHttpClientConnectionManager(registry); + } catch (Exception ex) { + throw new IllegalStateException("SSL configuration issue", ex); + } finally { + if (fin != null) { + try { + fin.close(); + } catch (IOException ignore) { + } + } + } + } + } + } + + private class ConnectionCleanerTask implements Runnable { + + private final int connectionIdleTimeout; + private final BasicTimer executionTimeStats; + private final Counter cleanupFailed; + + private ConnectionCleanerTask(int connectionIdleTimeout) { + this.connectionIdleTimeout = connectionIdleTimeout; + MonitorConfig.Builder monitorConfigBuilder = MonitorConfig.builder("Eureka-Connection-Cleaner-Time"); + executionTimeStats = new BasicTimer(monitorConfigBuilder.build()); + cleanupFailed = new BasicCounter(MonitorConfig.builder("Eureka-Connection-Cleaner-Failure").build()); + try { + Monitors.registerObject(this); + } catch (Exception e) { + s_logger.error("Unable to register with servo.", e); + } + } + + @Override + public void run() { + Stopwatch start = executionTimeStats.start(); + try { + HttpClientConnectionManager cm = (HttpClientConnectionManager) apacheHttpClient + .getConfiguration() + .getProperty(ApacheClientProperties.CONNECTION_MANAGER); + cm.closeIdleConnections(connectionIdleTimeout, TimeUnit.SECONDS); + } catch (Throwable e) { + s_logger.error("Cannot clean connections", e); + cleanupFailed.increment(); + } finally { + if (null != start) { + start.stop(); + } + } + + } + } +} \ No newline at end of file diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClient.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClient.java index 5f0e735f2..f431a53e4 100644 --- a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClient.java +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClient.java @@ -21,6 +21,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; + import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; @@ -143,6 +144,7 @@ public EurekaHttpResponse sendHeartBeat(String appName, String id, addExtraProperties(requestBuilder); addExtraHeaders(requestBuilder); response = requestBuilder.put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE)); // Jersey2 refuses to handle PUT with no body + requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE); EurekaHttpResponseBuilder eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response)); if (response.hasEntity()) { eurekaResponseBuilder.entity(response.readEntity(InstanceInfo.class)); diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClientFactory.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClientFactory.java index 77683d6d3..51c1f8b47 100644 --- a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClientFactory.java +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2ApplicationClientFactory.java @@ -25,9 +25,13 @@ import java.io.FileInputStream; import java.security.KeyStore; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; +import com.netflix.appinfo.AbstractEurekaIdentity; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.provider.DiscoveryJerseyProvider; import com.netflix.discovery.shared.resolver.EurekaEndpoint; import com.netflix.discovery.shared.transport.EurekaClientFactoryBuilder; @@ -38,6 +42,8 @@ import org.glassfish.jersey.client.JerseyClient; import static com.netflix.discovery.util.DiscoveryBuildInfo.buildVersion; +import com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory; +import com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder; /** * @author Tomasz Bak @@ -63,6 +69,31 @@ public EurekaHttpClient newClient(EurekaEndpoint endpoint) { public void shutdown() { jersey2Client.close(); } + + public static Jersey2ApplicationClientFactory create(EurekaClientConfig clientConfig, + Collection additionalFilters, + InstanceInfo myInstanceInfo, + AbstractEurekaIdentity clientIdentity) { + Jersey2ApplicationClientFactoryBuilder clientBuilder = newBuilder(); + clientBuilder.withAdditionalFilters(additionalFilters); + clientBuilder.withMyInstanceInfo(myInstanceInfo); + clientBuilder.withUserAgent("Java-EurekaClient"); + clientBuilder.withClientConfig(clientConfig); + clientBuilder.withClientIdentity(clientIdentity); + + if ("true".equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) { + clientBuilder.withClientName("DiscoveryClient-HTTPClient-System").withSystemSSLConfiguration(); + } else if (clientConfig.getProxyHost() != null && clientConfig.getProxyPort() != null) { + clientBuilder.withClientName("Proxy-DiscoveryClient-HTTPClient") + .withProxy( + clientConfig.getProxyHost(), Integer.parseInt(clientConfig.getProxyPort()), + clientConfig.getProxyUserName(), clientConfig.getProxyPassword()); + } else { + clientBuilder.withClientName("DiscoveryClient-HTTPClient"); + } + + return clientBuilder.build(); + } public static Jersey2ApplicationClientFactoryBuilder newBuilder() { return new Jersey2ApplicationClientFactoryBuilder(); @@ -71,21 +102,33 @@ public static Jersey2ApplicationClientFactoryBuilder newBuilder() { public static class Jersey2ApplicationClientFactoryBuilder extends EurekaClientFactoryBuilder { private List features = new ArrayList<>(); + private List additionalFilters = new ArrayList<>(); public Jersey2ApplicationClientFactoryBuilder withFeature(Feature feature) { features.add(feature); return this; } + Jersey2ApplicationClientFactoryBuilder withAdditionalFilters(Collection additionalFilters) { + if (additionalFilters != null) { + this.additionalFilters.addAll(additionalFilters); + } + return this; + } + @Override public Jersey2ApplicationClientFactory build() { ClientBuilder clientBuilder = ClientBuilder.newBuilder(); ClientConfig clientConfig = new ClientConfig(); + + for (ClientRequestFilter filter : additionalFilters) { + clientBuilder.register(filter); + } for (Feature feature : features) { clientConfig.register(feature); } - + addProviders(clientConfig); addSSLConfiguration(clientBuilder); addProxyConfiguration(clientConfig); diff --git a/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2TransportClientFactories.java b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2TransportClientFactories.java new file mode 100644 index 000000000..8fad011f6 --- /dev/null +++ b/eureka-client-jersey2/src/main/java/com/netflix/discovery/shared/transport/jersey2/Jersey2TransportClientFactories.java @@ -0,0 +1,57 @@ +package com.netflix.discovery.shared.transport.jersey2; + +import java.util.Collection; + +import javax.ws.rs.client.ClientRequestFilter; + +import com.netflix.appinfo.EurekaClientIdentity; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.discovery.shared.resolver.EurekaEndpoint; +import com.netflix.discovery.shared.transport.EurekaHttpClient; +import com.netflix.discovery.shared.transport.TransportClientFactory; +import com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient; +import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient; +import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; + +public class Jersey2TransportClientFactories implements TransportClientFactories { + + private static final Jersey2TransportClientFactories INSTANCE = new Jersey2TransportClientFactories(); + + public static Jersey2TransportClientFactories getInstance() { + return INSTANCE; + } + + @Override + public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig, + final Collection additionalFilters, + final InstanceInfo myInstanceInfo) { + final TransportClientFactory jerseyFactory = Jersey2ApplicationClientFactory.create( + clientConfig, + additionalFilters, + myInstanceInfo, + new EurekaClientIdentity(myInstanceInfo.getIPAddr()) + ); + final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory); + + return new TransportClientFactory() { + @Override + public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { + return metricsFactory.newClient(serviceUrl); + } + + @Override + public void shutdown() { + metricsFactory.shutdown(); + jerseyFactory.shutdown(); + } + }; + } + + @Override + public TransportClientFactory newTransportClientFactory(Collection additionalFilters, + EurekaJerseyClient providedJerseyClient) { + throw new UnsupportedOperationException(); + } + +} \ No newline at end of file diff --git a/eureka-client/src/main/java/com/netflix/discovery/AbstractDiscoveryClientOptionalArgs.java b/eureka-client/src/main/java/com/netflix/discovery/AbstractDiscoveryClientOptionalArgs.java new file mode 100644 index 000000000..1033e675a --- /dev/null +++ b/eureka-client/src/main/java/com/netflix/discovery/AbstractDiscoveryClientOptionalArgs.java @@ -0,0 +1,89 @@ +package com.netflix.discovery; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Provider; + +import com.google.inject.Inject; +import com.netflix.appinfo.HealthCheckCallback; +import com.netflix.appinfo.HealthCheckHandler; +import com.netflix.discovery.shared.transport.TransportClientFactory; +import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient; +import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; +import com.netflix.eventbus.spi.EventBus; + +/** + * The type for client supplied filters (supports jersey1 and jersey2) + */ +public abstract class AbstractDiscoveryClientOptionalArgs { + Provider healthCheckCallbackProvider; + + Provider healthCheckHandlerProvider; + + Collection additionalFilters; + + EurekaJerseyClient eurekaJerseyClient; + + TransportClientFactory transportClientFactory; + + TransportClientFactories transportClientFactories; + + private Set eventListeners; + + @Inject(optional = true) + public void setEventListeners(Set listeners) { + if (eventListeners == null) { + eventListeners = new HashSet<>(); + } + eventListeners.addAll(listeners); + } + + @Inject(optional = true) + public void setEventBus(final EventBus eventBus) { + if (eventListeners == null) { + eventListeners = new HashSet<>(); + } + + eventListeners.add(new EurekaEventListener() { + @Override + public void onEvent(EurekaEvent event) { + eventBus.publish(event); + } + }); + } + + @Inject(optional = true) + public void setHealthCheckCallbackProvider(Provider healthCheckCallbackProvider) { + this.healthCheckCallbackProvider = healthCheckCallbackProvider; + } + + @Inject(optional = true) + public void setHealthCheckHandlerProvider(Provider healthCheckHandlerProvider) { + this.healthCheckHandlerProvider = healthCheckHandlerProvider; + } + + @Inject(optional = true) + public void setAdditionalFilters(Collection additionalFilters) { + this.additionalFilters = additionalFilters; + } + + @Inject(optional = true) + public void setEurekaJerseyClient(EurekaJerseyClient eurekaJerseyClient) { + this.eurekaJerseyClient = eurekaJerseyClient; + } + + Set getEventListeners() { + return eventListeners == null ? Collections.emptySet() : eventListeners; + } + + public TransportClientFactories getTransportClientFactories() { + return transportClientFactories; + } + + public void setTransportClientFactories(TransportClientFactories transportClientFactories) { + this.transportClientFactories = transportClientFactories; + } +} \ No newline at end of file diff --git a/eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java b/eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java index c2bb5327d..f73442421 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java +++ b/eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java @@ -76,7 +76,6 @@ import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient; import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; import com.netflix.discovery.util.ThresholdLevelsMetric; -import com.netflix.eventbus.spi.EventBus; import com.netflix.servo.annotations.DataSourceType; import com.netflix.servo.monitor.Counter; import com.netflix.servo.monitor.Monitors; @@ -218,64 +217,6 @@ void shutdown() { } } - public static class DiscoveryClientOptionalArgs { - private Provider healthCheckCallbackProvider; - - private Provider healthCheckHandlerProvider; - - private Collection additionalFilters; - - private EurekaJerseyClient eurekaJerseyClient; - - private Set eventListeners; - - @Inject(optional = true) - public void setEventListeners(Set listeners) { - if (eventListeners == null) { - eventListeners = new HashSet<>(); - } - eventListeners.addAll(listeners); - } - - @Inject(optional = true) - public void setEventBus(final EventBus eventBus) { - if (eventListeners == null) { - eventListeners = new HashSet<>(); - } - - eventListeners.add(new EurekaEventListener() { - @Override - public void onEvent(EurekaEvent event) { - eventBus.publish(event); - } - }); - } - - @Inject(optional = true) - public void setHealthCheckCallbackProvider(Provider healthCheckCallbackProvider) { - this.healthCheckCallbackProvider = healthCheckCallbackProvider; - } - - @Inject(optional = true) - public void setHealthCheckHandlerProvider(Provider healthCheckHandlerProvider) { - this.healthCheckHandlerProvider = healthCheckHandlerProvider; - } - - @Inject(optional = true) - public void setAdditionalFilters(Collection additionalFilters) { - this.additionalFilters = additionalFilters; - } - - @Inject(optional = true) - public void setEurekaJerseyClient(EurekaJerseyClient eurekaJerseyClient) { - this.eurekaJerseyClient = eurekaJerseyClient; - } - - Set getEventListeners() { - return eventListeners == null ? Collections.emptySet() : eventListeners; - } - } - /** * Assumes applicationInfoManager is already initialized * @@ -292,7 +233,7 @@ public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config) { * @deprecated use constructor that takes ApplicationInfoManager instead of InstanceInfo directly */ @Deprecated - public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config, DiscoveryClientOptionalArgs args) { + public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) { this(ApplicationInfoManager.getInstance(), config, args); } @@ -300,7 +241,7 @@ public DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClie this(applicationInfoManager, config, null); } - public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, DiscoveryClientOptionalArgs args) { + public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) { this(applicationInfoManager, config, args, new Provider() { private volatile BackupRegistry backupRegistryInstance; @@ -333,7 +274,7 @@ public synchronized BackupRegistry get() { } @Inject - DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, DiscoveryClientOptionalArgs args, + DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider backupRegistryProvider) { if (args != null) { this.healthCheckHandlerProvider = args.healthCheckHandlerProvider; @@ -463,19 +404,26 @@ public synchronized BackupRegistry get() { } private void scheduleServerEndpointTask(EurekaTransport eurekaTransport, - DiscoveryClientOptionalArgs args) { + AbstractDiscoveryClientOptionalArgs args) { - Collection additionalFilters = args == null - ? Collections.emptyList() + + Collection additionalFilters = args == null + ? Collections.emptyList() : args.additionalFilters; EurekaJerseyClient providedJerseyClient = args == null ? null : args.eurekaJerseyClient; - + + TransportClientFactories transportClientFactories = TransportClientFactories.INSTANCE; + if (args != null && args.getTransportClientFactories() != null) { + transportClientFactories = args.getTransportClientFactories(); + } + + // If the transport factory was not supplied with args, assume they are using jersey 1 for passivity eurekaTransport.transportClientFactory = providedJerseyClient == null - ? TransportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo()) - : TransportClientFactories.newTransportClientFactory(additionalFilters, providedJerseyClient); + ? transportClientFactories.newTransportClientFactory(clientConfig, (Collection) additionalFilters, applicationInfoManager.getInfo()) + : transportClientFactories.newTransportClientFactory((Collection) additionalFilters, providedJerseyClient); ApplicationsResolver.ApplicationsSource applicationsSource = new ApplicationsResolver.ApplicationsSource() { @Override @@ -543,6 +491,10 @@ public Applications getApplications(int stalenessThreshold, TimeUnit timeUnit) { public EurekaClientConfig getEurekaClientConfig() { return clientConfig; } + + public ApplicationInfoManager getApplicationInfoManager() { + return applicationInfoManager; + } /* * (non-Javadoc) diff --git a/eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java b/eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java index a48c1d50e..4953cdf75 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java +++ b/eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java @@ -19,7 +19,6 @@ import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.InstanceInfo; -import com.netflix.discovery.DiscoveryClient.DiscoveryClientOptionalArgs; import com.netflix.discovery.shared.LookupService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +75,7 @@ public void setEurekaInstanceConfig(EurekaInstanceConfig eurekaInstanceConfig) { * @param eurekaConfig the eureka client configuration of the instance. */ public void initComponent(EurekaInstanceConfig config, - EurekaClientConfig eurekaConfig, DiscoveryClientOptionalArgs args) { + EurekaClientConfig eurekaConfig, AbstractDiscoveryClientOptionalArgs args) { this.eurekaInstanceConfig = config; this.eurekaClientConfig = eurekaConfig; if (ApplicationInfoManager.getInstance().getInfo() == null) { diff --git a/eureka-client/src/main/java/com/netflix/discovery/guice/EurekaModule.java b/eureka-client/src/main/java/com/netflix/discovery/guice/EurekaModule.java index 1b4d44046..f7da4f3ce 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/guice/EurekaModule.java +++ b/eureka-client/src/main/java/com/netflix/discovery/guice/EurekaModule.java @@ -8,9 +8,11 @@ import com.netflix.appinfo.providers.CloudInstanceConfigProvider; import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider; import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.providers.DefaultEurekaClientConfigProvider; +import com.netflix.discovery.shared.transport.jersey.DiscoveryClientOptionalArgs; /** * @author David Liu @@ -32,6 +34,9 @@ protected void configure() { bind(InstanceInfo.class).toProvider(EurekaConfigBasedInstanceInfoProvider.class).in(Scopes.SINGLETON); bind(EurekaClient.class).to(DiscoveryClient.class).in(Scopes.SINGLETON); + + // Default to the jersey1 discovery client optional args + bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClientOptionalArgs.class).in(Scopes.SINGLETON); } @Override diff --git a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/EurekaClientFactoryBuilder.java b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/EurekaClientFactoryBuilder.java index e88c0517a..4904f9d18 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/EurekaClientFactoryBuilder.java +++ b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/EurekaClientFactoryBuilder.java @@ -5,7 +5,6 @@ import com.netflix.appinfo.AbstractEurekaIdentity; import com.netflix.appinfo.EurekaAccept; import com.netflix.appinfo.InstanceInfo; -import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.converters.wrappers.CodecWrappers; import com.netflix.discovery.converters.wrappers.DecoderWrapper; @@ -41,6 +40,18 @@ public abstract class EurekaClientFactoryBuilder { + +} diff --git a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/Jersey1TransportClientFactories.java b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/Jersey1TransportClientFactories.java new file mode 100644 index 000000000..5f41a8018 --- /dev/null +++ b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/Jersey1TransportClientFactories.java @@ -0,0 +1,69 @@ +package com.netflix.discovery.shared.transport.jersey; + +import java.util.Collection; + +import com.netflix.appinfo.EurekaClientIdentity; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.discovery.shared.resolver.EurekaEndpoint; +import com.netflix.discovery.shared.transport.EurekaHttpClient; +import com.netflix.discovery.shared.transport.TransportClientFactory; +import com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient; +import com.sun.jersey.api.client.filter.ClientFilter; +import com.sun.jersey.client.apache4.ApacheHttpClient4; + +public class Jersey1TransportClientFactories implements TransportClientFactories { + @Deprecated + public TransportClientFactory newTransportClientFactory(final Collection additionalFilters, + final EurekaJerseyClient providedJerseyClient) { + ApacheHttpClient4 apacheHttpClient = providedJerseyClient.getClient(); + if (additionalFilters != null) { + for (ClientFilter filter : additionalFilters) { + if (filter != null) { + apacheHttpClient.addFilter(filter); + } + } + } + + final TransportClientFactory jerseyFactory = new JerseyEurekaHttpClientFactory(providedJerseyClient, false); + final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory); + + return new TransportClientFactory() { + @Override + public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { + return metricsFactory.newClient(serviceUrl); + } + + @Override + public void shutdown() { + metricsFactory.shutdown(); + jerseyFactory.shutdown(); + } + }; + } + + public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig, + final Collection additionalFilters, + final InstanceInfo myInstanceInfo) { + final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create( + clientConfig, + additionalFilters, + myInstanceInfo, + new EurekaClientIdentity(myInstanceInfo.getIPAddr()) + ); + final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory); + + return new TransportClientFactory() { + @Override + public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { + return metricsFactory.newClient(serviceUrl); + } + + @Override + public void shutdown() { + metricsFactory.shutdown(); + jerseyFactory.shutdown(); + } + }; + } +} \ No newline at end of file diff --git a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/JerseyEurekaHttpClientFactory.java b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/JerseyEurekaHttpClientFactory.java index 2b202b6af..5cfbd6925 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/JerseyEurekaHttpClientFactory.java +++ b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/JerseyEurekaHttpClientFactory.java @@ -117,15 +117,7 @@ public static JerseyEurekaHttpClientFactory create(EurekaClientConfig clientConf .withAdditionalFilters(additionalFilters) .withMyInstanceInfo(myInstanceInfo) .withUserAgent("Java-EurekaClient") - .withClientAccept(EurekaAccept.fromString(clientConfig.getClientDataAccept())) - .withAllowRedirect(clientConfig.allowRedirects()) - .withConnectionTimeout(clientConfig.getEurekaServerConnectTimeoutSeconds() * 1000) - .withReadTimeout(clientConfig.getEurekaServerReadTimeoutSeconds() * 1000) - .withMaxConnectionsPerHost(clientConfig.getEurekaServerTotalConnectionsPerHost()) - .withMaxTotalConnections(clientConfig.getEurekaServerTotalConnections()) - .withConnectionIdleTimeout(clientConfig.getEurekaConnectionIdleTimeoutSeconds() * 1000) - .withEncoder(clientConfig.getEncoderName()) - .withDecoder(clientConfig.getDecoderName(), clientConfig.getClientDataAccept()) + .withClientConfig(clientConfig) .withClientIdentity(clientIdentity); if ("true".equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) { diff --git a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/TransportClientFactories.java b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/TransportClientFactories.java index 9bafa3aca..1fb97c3ce 100644 --- a/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/TransportClientFactories.java +++ b/eureka-client/src/main/java/com/netflix/discovery/shared/transport/jersey/TransportClientFactories.java @@ -1,71 +1,21 @@ package com.netflix.discovery.shared.transport.jersey; -import com.netflix.appinfo.EurekaClientIdentity; +import java.util.Collection; + import com.netflix.appinfo.InstanceInfo; import com.netflix.discovery.EurekaClientConfig; -import com.netflix.discovery.shared.resolver.EurekaEndpoint; -import com.netflix.discovery.shared.transport.EurekaHttpClient; import com.netflix.discovery.shared.transport.TransportClientFactory; -import com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient; import com.sun.jersey.api.client.filter.ClientFilter; -import com.sun.jersey.client.apache4.ApacheHttpClient4; - -import java.util.Collection; - -public final class TransportClientFactories { +public interface TransportClientFactories { + + static final TransportClientFactories INSTANCE = new Jersey1TransportClientFactories(); + @Deprecated - public static TransportClientFactory newTransportClientFactory(final Collection additionalFilters, - final EurekaJerseyClient providedJerseyClient) { - ApacheHttpClient4 apacheHttpClient = providedJerseyClient.getClient(); - if (additionalFilters != null) { - for (ClientFilter filter : additionalFilters) { - if (filter != null) { - apacheHttpClient.addFilter(filter); - } - } - } - - final TransportClientFactory jerseyFactory = new JerseyEurekaHttpClientFactory(providedJerseyClient, false); - final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory); - - return new TransportClientFactory() { - @Override - public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { - return metricsFactory.newClient(serviceUrl); - } - - @Override - public void shutdown() { - metricsFactory.shutdown(); - jerseyFactory.shutdown(); - } - }; - } - - public static TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig, - final Collection additionalFilters, - final InstanceInfo myInstanceInfo) { - final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create( - clientConfig, - additionalFilters, - myInstanceInfo, - new EurekaClientIdentity(myInstanceInfo.getIPAddr()) - ); - final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory); - - return new TransportClientFactory() { - @Override - public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { - return metricsFactory.newClient(serviceUrl); - } - - @Override - public void shutdown() { - metricsFactory.shutdown(); - jerseyFactory.shutdown(); - } - }; - } + public TransportClientFactory newTransportClientFactory(final Collection additionalFilters, + final EurekaJerseyClient providedJerseyClient); + public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig, + final Collection additionalFilters, + final InstanceInfo myInstanceInfo); } \ No newline at end of file diff --git a/eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientOptionalArgsTest.java b/eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientOptionalArgsTest.java index bf3d77b02..39ec66792 100644 --- a/eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientOptionalArgsTest.java +++ b/eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientOptionalArgsTest.java @@ -2,38 +2,42 @@ import javax.inject.Provider; +import org.junit.Before; import org.junit.Test; import com.netflix.appinfo.HealthCheckCallback; import com.netflix.appinfo.HealthCheckHandler; -import com.netflix.discovery.DiscoveryClient.DiscoveryClientOptionalArgs; +import com.netflix.discovery.shared.transport.jersey.DiscoveryClientOptionalArgs; /** * @author Matt Nelson */ public class DiscoveryClientOptionalArgsTest { + private DiscoveryClientOptionalArgs args; + + @Before + public void before() { + args = new DiscoveryClientOptionalArgs(); + } + @Test public void testHealthCheckCallbackGuiceProvider() { - DiscoveryClientOptionalArgs args = new DiscoveryClientOptionalArgs(); args.setHealthCheckCallbackProvider(new GuiceProvider()); } @Test public void testHealthCheckCallbackJavaxProvider() { - DiscoveryClientOptionalArgs args = new DiscoveryClientOptionalArgs(); args.setHealthCheckCallbackProvider(new JavaxProvider()); } @Test public void testHealthCheckHandlerGuiceProvider() { - DiscoveryClientOptionalArgs args = new DiscoveryClientOptionalArgs(); args.setHealthCheckHandlerProvider(new GuiceProvider()); } @Test public void testHealthCheckHandlerJavaxProvider() { - DiscoveryClientOptionalArgs args = new DiscoveryClientOptionalArgs(); args.setHealthCheckHandlerProvider(new JavaxProvider()); } diff --git a/eureka-client/src/test/java/com/netflix/discovery/EurekaClientLifecycleTest.java b/eureka-client/src/test/java/com/netflix/discovery/EurekaClientLifecycleTest.java index f27274af1..9a734f07e 100644 --- a/eureka-client/src/test/java/com/netflix/discovery/EurekaClientLifecycleTest.java +++ b/eureka-client/src/test/java/com/netflix/discovery/EurekaClientLifecycleTest.java @@ -7,6 +7,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Injector; +import com.google.inject.Scopes; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.PropertiesInstanceConfig; @@ -15,6 +16,7 @@ import com.netflix.discovery.shared.transport.EurekaHttpClient; import com.netflix.discovery.shared.transport.EurekaHttpResponse; import com.netflix.discovery.shared.transport.SimpleEurekaHttpServer; +import com.netflix.discovery.shared.transport.jersey.DiscoveryClientOptionalArgs; import com.netflix.discovery.util.InstanceInfoGenerator; import com.netflix.governator.guice.LifecycleInjector; import com.netflix.governator.lifecycle.LifecycleManager; @@ -82,6 +84,7 @@ public void testEurekaClientLifecycle() throws Exception { protected void configure() { bind(EurekaInstanceConfig.class).to(LocalEurekaInstanceConfig.class); bind(EurekaClientConfig.class).to(LocalEurekaClientConfig.class); + bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClientOptionalArgs.class).in(Scopes.SINGLETON); } } ) @@ -117,6 +120,7 @@ protected void configure() { bind(EurekaInstanceConfig.class).to(LocalEurekaInstanceConfig.class); bind(EurekaClientConfig.class).to(BadServerEurekaClientConfig.class); bind(BackupRegistry.class).toInstance(backupRegistry); + bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClientOptionalArgs.class).in(Scopes.SINGLETON); } } ) diff --git a/eureka-client/src/test/java/com/netflix/discovery/shared/transport/EurekaHttpClientsTest.java b/eureka-client/src/test/java/com/netflix/discovery/shared/transport/EurekaHttpClientsTest.java index 65bdf68b1..c62c3ff2a 100644 --- a/eureka-client/src/test/java/com/netflix/discovery/shared/transport/EurekaHttpClientsTest.java +++ b/eureka-client/src/test/java/com/netflix/discovery/shared/transport/EurekaHttpClientsTest.java @@ -109,7 +109,7 @@ public void setUp() throws IOException { "test", transportConfig, clusterResolver, - TransportClientFactories.newTransportClientFactory( + TransportClientFactories.INSTANCE.newTransportClientFactory( clientConfig, Collections.emptyList(), applicationInfoManager.getInfo() @@ -270,7 +270,7 @@ public void testAddingAdditionalFilters() throws Exception { TestFilter testFilter = new TestFilter(); Collection additionalFilters = Arrays.asList(testFilter); - TransportClientFactory transportClientFactory = TransportClientFactories.newTransportClientFactory( + TransportClientFactory transportClientFactory = TransportClientFactories.INSTANCE.newTransportClientFactory( clientConfig, additionalFilters, MY_INSTANCE diff --git a/eureka-core-jersey2/build.gradle b/eureka-core-jersey2/build.gradle new file mode 100644 index 000000000..1eddab41f --- /dev/null +++ b/eureka-core-jersey2/build.gradle @@ -0,0 +1,18 @@ +configurations.all { + exclude module: 'jsr311-api' + exclude group: 'com.sun.jersey' + exclude group: 'com.sun.jersey.contribs' +} + +dependencies { + compile project(':eureka-core') + compile project(':eureka-client-jersey2') + compile 'org.glassfish.jersey.core:jersey-client:2.23.1' + compile 'org.glassfish.jersey.connectors:jersey-apache-connector:2.23.1' + + testCompile project(':eureka-test-utils') + testCompile "junit:junit:${junit_version}" + testCompile "org.mock-server:mockserver-netty:${mockserverVersion}" + testCompile "org.mockito:mockito-core:${mockitoVersion}" + testRuntime 'org.slf4j:slf4j-simple:1.7.10' +} diff --git a/eureka-core-jersey2/src/main/java/com/netflix/eureka/Jersey2EurekaBootStrap.java b/eureka-core-jersey2/src/main/java/com/netflix/eureka/Jersey2EurekaBootStrap.java new file mode 100644 index 000000000..d17078327 --- /dev/null +++ b/eureka-core-jersey2/src/main/java/com/netflix/eureka/Jersey2EurekaBootStrap.java @@ -0,0 +1,35 @@ +package com.netflix.eureka; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.eureka.cluster.Jersey2PeerEurekaNodes; +import com.netflix.eureka.cluster.PeerEurekaNodes; +import com.netflix.eureka.registry.PeerAwareInstanceRegistry; +import com.netflix.eureka.resources.ServerCodecs; + +/** + * Jersey2 eureka server bootstrapper + * @author Matt Nelson + */ +public class Jersey2EurekaBootStrap extends EurekaBootStrap { + + public Jersey2EurekaBootStrap(DiscoveryClient discoveryClient) { + super(); + setDiscoveryClient(discoveryClient); + } + + @Override + protected PeerEurekaNodes getPeerEurekaNodes(PeerAwareInstanceRegistry registry, EurekaServerConfig eurekaServerConfig, EurekaClientConfig eurekaClientConfig, ServerCodecs serverCodecs, ApplicationInfoManager applicationInfoManager) { + PeerEurekaNodes peerEurekaNodes = new Jersey2PeerEurekaNodes( + registry, + eurekaServerConfig, + eurekaClientConfig, + serverCodecs, + applicationInfoManager + ); + + return peerEurekaNodes; + } + +} diff --git a/eureka-core-jersey2/src/main/java/com/netflix/eureka/cluster/Jersey2PeerEurekaNodes.java b/eureka-core-jersey2/src/main/java/com/netflix/eureka/cluster/Jersey2PeerEurekaNodes.java new file mode 100644 index 000000000..4524dac27 --- /dev/null +++ b/eureka-core-jersey2/src/main/java/com/netflix/eureka/cluster/Jersey2PeerEurekaNodes.java @@ -0,0 +1,30 @@ +package com.netflix.eureka.cluster; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.eureka.EurekaServerConfig; +import com.netflix.eureka.registry.PeerAwareInstanceRegistry; +import com.netflix.eureka.resources.ServerCodecs; +import com.netflix.eureka.transport.Jersey2ReplicationClient; + +/** + * Jersey2 implementation of PeerEurekaNodes that uses the Jersey2 replication client + * @author Matt Nelson + */ +public class Jersey2PeerEurekaNodes extends PeerEurekaNodes { + + public Jersey2PeerEurekaNodes(PeerAwareInstanceRegistry registry, EurekaServerConfig serverConfig, + EurekaClientConfig clientConfig, ServerCodecs serverCodecs, ApplicationInfoManager applicationInfoManager) { + super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager); + } + + @Override + protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) { + HttpReplicationClient replicationClient = Jersey2ReplicationClient.createReplicationClient(serverConfig, serverCodecs, peerEurekaNodeUrl); + String targetHost = hostFromUrl(peerEurekaNodeUrl); + if (targetHost == null) { + targetHost = "host"; + } + return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl, replicationClient, serverConfig); + } +} diff --git a/eureka-core-jersey2/src/main/java/com/netflix/eureka/resources/EurekaServerContextBinder.java b/eureka-core-jersey2/src/main/java/com/netflix/eureka/resources/EurekaServerContextBinder.java new file mode 100644 index 000000000..4f560946a --- /dev/null +++ b/eureka-core-jersey2/src/main/java/com/netflix/eureka/resources/EurekaServerContextBinder.java @@ -0,0 +1,33 @@ +package com.netflix.eureka.resources; + +import org.glassfish.hk2.api.Factory; +import org.glassfish.hk2.utilities.binding.AbstractBinder; + +import com.netflix.eureka.EurekaServerContext; +import com.netflix.eureka.EurekaServerContextHolder; + +/** + * Jersey2 binder for the EurekaServerContext. Replaces the GuiceFilter in the server WAR web.xml + * @author Matt Nelson + */ +public class EurekaServerContextBinder extends AbstractBinder { + + public class EurekaServerContextFactory implements Factory { + @Override + public EurekaServerContext provide() { + return EurekaServerContextHolder.getInstance().getServerContext(); + } + + @Override + public void dispose(EurekaServerContext t) { + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void configure() { + bindFactory(new EurekaServerContextFactory()).to(EurekaServerContext.class); + } +} diff --git a/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2DynamicGZIPContentEncodingFilter.java b/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2DynamicGZIPContentEncodingFilter.java new file mode 100644 index 000000000..f0108dd5e --- /dev/null +++ b/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2DynamicGZIPContentEncodingFilter.java @@ -0,0 +1,50 @@ +package com.netflix.eureka.transport; + +import com.netflix.eureka.EurekaServerConfig; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.client.ClientResponseFilter; +import javax.ws.rs.core.HttpHeaders; +import java.io.IOException; + +public class Jersey2DynamicGZIPContentEncodingFilter implements ClientRequestFilter, ClientResponseFilter { + + private final EurekaServerConfig config; + + public Jersey2DynamicGZIPContentEncodingFilter(EurekaServerConfig config) { + this.config = config; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + if (!requestContext.getHeaders().containsKey(HttpHeaders.ACCEPT_ENCODING)) { + requestContext.getHeaders().add(HttpHeaders.ACCEPT_ENCODING, "gzip"); + } + + if (hasEntity(requestContext) && isCompressionEnabled()) { + Object contentEncoding = requestContext.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING); + if (!"gzip".equals(contentEncoding)) { + requestContext.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip"); + } + } + } + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + Object contentEncoding = responseContext.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING); + if ("gzip".equals(contentEncoding)) { + responseContext.getHeaders().remove(HttpHeaders.CONTENT_ENCODING); + } + } + + private boolean hasEntity(ClientRequestContext requestContext) { + return false; + } + + private boolean isCompressionEnabled() { + return config.shouldEnableReplicatedRequestCompression(); + } + +} \ No newline at end of file diff --git a/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2ReplicationClient.java b/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2ReplicationClient.java new file mode 100644 index 000000000..ffe827e8a --- /dev/null +++ b/eureka-core-jersey2/src/main/java/com/netflix/eureka/transport/Jersey2ReplicationClient.java @@ -0,0 +1,184 @@ +package com.netflix.eureka.transport; + +import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse; + +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.discovery.shared.transport.EurekaHttpResponse; +import com.netflix.discovery.shared.transport.jersey2.AbstractJersey2EurekaHttpClient; +import com.netflix.discovery.shared.transport.jersey2.EurekaIdentityHeaderFilter; +import com.netflix.discovery.shared.transport.jersey2.EurekaJersey2Client; +import com.netflix.discovery.shared.transport.jersey2.EurekaJersey2ClientImpl; +import com.netflix.eureka.EurekaServerConfig; +import com.netflix.eureka.EurekaServerIdentity; +import com.netflix.eureka.cluster.HttpReplicationClient; +import com.netflix.eureka.cluster.PeerEurekaNode; +import com.netflix.eureka.cluster.protocol.ReplicationList; +import com.netflix.eureka.cluster.protocol.ReplicationListResponse; +import com.netflix.eureka.resources.ASGResource.ASGStatus; +import com.netflix.eureka.resources.ServerCodecs; + +/** + * @author Tomasz Bak + */ +public class Jersey2ReplicationClient extends AbstractJersey2EurekaHttpClient implements HttpReplicationClient { + + private static final Logger logger = LoggerFactory.getLogger(Jersey2ReplicationClient.class); + + public Jersey2ReplicationClient(EurekaJersey2Client jerseyClient, String serviceUrl) { + super(jerseyClient, serviceUrl, true); + } + + @Override + protected void addExtraHeaders(Builder webResource) { + webResource.header(PeerEurekaNode.HEADER_REPLICATION, "true"); + } + + /** + * Compared to regular heartbeat, in the replication channel the server may return a more up to date + * instance copy. + */ + @Override + public EurekaHttpResponse sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) { + String urlPath = "apps/" + appName + '/' + id; + Response response = null; + try { + WebTarget webResource = jerseyClient.getClient().target(serviceUrl) + .path(urlPath) + .queryParam("status", info.getStatus().toString()) + .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()); + if (overriddenStatus != null) { + webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name()); + } + Builder requestBuilder = webResource.request(); + addExtraHeaders(requestBuilder); + response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).put(Entity.entity("{}", MediaType.APPLICATION_JSON_TYPE)); // Jersey2 refuses to handle PUT with no body + InstanceInfo infoFromPeer = null; + if (response.getStatus() == Status.CONFLICT.getStatusCode() && response.hasEntity()) { + infoFromPeer = response.readEntity(InstanceInfo.class); + } + return anEurekaHttpResponse(response.getStatus(), infoFromPeer).type(MediaType.APPLICATION_JSON_TYPE).build(); + } finally { + if (logger.isDebugEnabled()) { + logger.debug("[heartbeat] Jersey HTTP PUT {}; statusCode={}", urlPath, response == null ? "N/A" : response.getStatus()); + } + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse statusUpdate(String asgName, ASGStatus newStatus) { + Response response = null; + try { + String urlPath = "asg/" + asgName + "/status"; + response = jerseyClient.getClient().target(serviceUrl) + .path(urlPath) + .queryParam("value", newStatus.name()) + .request() + .header(PeerEurekaNode.HEADER_REPLICATION, "true") + .put(Entity.text("")); + return EurekaHttpResponse.status(response.getStatus()); + } finally { + if (response != null) { + response.close(); + } + } + } + + @Override + public EurekaHttpResponse submitBatchUpdates(ReplicationList replicationList) { + Response response = null; + try { + response = jerseyClient.getClient().target(serviceUrl) + .path(PeerEurekaNode.BATCH_URL_PATH) + .request(MediaType.APPLICATION_JSON_TYPE) + .post(Entity.json(replicationList)); + if (!isSuccess(response.getStatus())) { + return anEurekaHttpResponse(response.getStatus(), ReplicationListResponse.class).build(); + } + ReplicationListResponse batchResponse = response.readEntity(ReplicationListResponse.class); + return anEurekaHttpResponse(response.getStatus(), batchResponse).type(MediaType.APPLICATION_JSON_TYPE).build(); + } finally { + if (response != null) { + response.close(); + } + } + } + + @Override + public void shutdown() { + super.shutdown(); + jerseyClient.destroyResources(); + } + + public static Jersey2ReplicationClient createReplicationClient(EurekaServerConfig config, ServerCodecs serverCodecs, String serviceUrl) { + String name = Jersey2ReplicationClient.class.getSimpleName() + ": " + serviceUrl + "apps/: "; + + EurekaJersey2Client jerseyClient; + try { + String hostname; + try { + hostname = new URL(serviceUrl).getHost(); + } catch (MalformedURLException e) { + hostname = serviceUrl; + } + + String jerseyClientName = "Discovery-PeerNodeClient-" + hostname; + EurekaJersey2ClientImpl.EurekaJersey2ClientBuilder clientBuilder = new EurekaJersey2ClientImpl.EurekaJersey2ClientBuilder() + .withClientName(jerseyClientName) + .withUserAgent("Java-EurekaClient-Replication") + .withEncoderWrapper(serverCodecs.getFullJsonCodec()) + .withDecoderWrapper(serverCodecs.getFullJsonCodec()) + .withConnectionTimeout(config.getPeerNodeConnectTimeoutMs()) + .withReadTimeout(config.getPeerNodeReadTimeoutMs()) + .withMaxConnectionsPerHost(config.getPeerNodeTotalConnectionsPerHost()) + .withMaxTotalConnections(config.getPeerNodeTotalConnections()) + .withConnectionIdleTimeout(config.getPeerNodeConnectionIdleTimeoutSeconds()); + + if (serviceUrl.startsWith("https://") && + "true".equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) { + clientBuilder.withSystemSSLConfiguration(); + } + jerseyClient = clientBuilder.build(); + } catch (Throwable e) { + throw new RuntimeException("Cannot Create new Replica Node :" + name, e); + } + + String ip = null; + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + logger.warn("Cannot find localhost ip", e); + } + + Client jerseyApacheClient = jerseyClient.getClient(); + jerseyApacheClient.register(new Jersey2DynamicGZIPContentEncodingFilter(config)); + + EurekaServerIdentity identity = new EurekaServerIdentity(ip); + jerseyApacheClient.register(new EurekaIdentityHeaderFilter(identity)); + + return new Jersey2ReplicationClient(jerseyClient, serviceUrl); + } + + private static boolean isSuccess(int statusCode) { + return statusCode >= 200 && statusCode < 300; + } +} diff --git a/eureka-core-jersey2/src/test/java/com/netflix/eureka/transport/Jersey2ReplicationClientTest.java b/eureka-core-jersey2/src/test/java/com/netflix/eureka/transport/Jersey2ReplicationClientTest.java new file mode 100644 index 000000000..543bdc95f --- /dev/null +++ b/eureka-core-jersey2/src/test/java/com/netflix/eureka/transport/Jersey2ReplicationClientTest.java @@ -0,0 +1,191 @@ +package com.netflix.eureka.transport; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.discovery.converters.EurekaJacksonCodec; +import com.netflix.discovery.shared.transport.ClusterSampleData; +import com.netflix.discovery.shared.transport.EurekaHttpResponse; +import com.netflix.eureka.DefaultEurekaServerConfig; +import com.netflix.eureka.EurekaServerConfig; +import com.netflix.eureka.cluster.PeerEurekaNode; +import com.netflix.eureka.resources.ASGResource.ASGStatus; +import com.netflix.eureka.resources.DefaultServerCodecs; +import com.netflix.eureka.resources.ServerCodecs; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockserver.client.server.MockServerClient; +import org.mockserver.junit.MockServerRule; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockserver.model.Header.header; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +/** + * Ideally we would test client/server REST layer together as an integration test, where server side has mocked + * service layer. Right now server side REST has to much logic, so this test would be equal to testing everything. + * Here we test only client side REST communication. + * + * @author Tomasz Bak + */ +public class Jersey2ReplicationClientTest { + + @Rule + public MockServerRule serverMockRule = new MockServerRule(this); + private MockServerClient serverMockClient; + + private Jersey2ReplicationClient replicationClient; + + private final EurekaServerConfig config = new DefaultEurekaServerConfig(); + private final ServerCodecs serverCodecs = new DefaultServerCodecs(config); + private final InstanceInfo instanceInfo = ClusterSampleData.newInstanceInfo(1); + + @Before + public void setUp() throws Exception { + replicationClient = Jersey2ReplicationClient.createReplicationClient( + config, serverCodecs, "http://localhost:" + serverMockRule.getHttpPort() + "/eureka/v2" + ); + } + + @After + public void tearDown() { + if (serverMockClient != null) { + serverMockClient.reset(); + } + } + + @Test + public void testRegistrationReplication() throws Exception { + serverMockClient.when( + request() + .withMethod("POST") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + instanceInfo.getAppName()) + ).respond( + response().withStatusCode(200) + ); + + EurekaHttpResponse response = replicationClient.register(instanceInfo); + assertThat(response.getStatusCode(), is(equalTo(200))); + } + + @Test + public void testCancelReplication() throws Exception { + serverMockClient.when( + request() + .withMethod("DELETE") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + instanceInfo.getAppName() + '/' + instanceInfo.getId()) + ).respond( + response().withStatusCode(204) + ); + + EurekaHttpResponse response = replicationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId()); + assertThat(response.getStatusCode(), is(equalTo(204))); + } + + @Test + public void testHeartbeatReplicationWithNoResponseBody() throws Exception { + serverMockClient.when( + request() + .withMethod("PUT") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + instanceInfo.getAppName() + '/' + instanceInfo.getId()) + ).respond( + response().withStatusCode(200) + ); + + EurekaHttpResponse response = replicationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, InstanceStatus.DOWN); + assertThat(response.getStatusCode(), is(equalTo(200))); + assertThat(response.getEntity(), is(nullValue())); + } + + @Test + public void testHeartbeatReplicationWithResponseBody() throws Exception { + InstanceInfo remoteInfo = new InstanceInfo(this.instanceInfo); + remoteInfo.setStatus(InstanceStatus.DOWN); + byte[] responseBody = toGzippedJson(remoteInfo); + + serverMockClient.when( + request() + .withMethod("PUT") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + this.instanceInfo.getAppName() + '/' + this.instanceInfo.getId()) + ).respond( + response() + .withStatusCode(Status.CONFLICT.getStatusCode()) + .withHeader(header("Content-Type", MediaType.APPLICATION_JSON)) + .withHeader(header("Content-Encoding", "gzip")) + .withBody(responseBody) + ); + + EurekaHttpResponse response = replicationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, null); + assertThat(response.getStatusCode(), is(equalTo(Status.CONFLICT.getStatusCode()))); + assertThat(response.getEntity(), is(notNullValue())); + } + + @Test + public void testAsgStatusUpdateReplication() throws Exception { + serverMockClient.when( + request() + .withMethod("PUT") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/asg/" + instanceInfo.getASGName() + "/status") + ).respond( + response().withStatusCode(200) + ); + + EurekaHttpResponse response = replicationClient.statusUpdate(instanceInfo.getASGName(), ASGStatus.ENABLED); + assertThat(response.getStatusCode(), is(equalTo(200))); + } + + @Test + public void testStatusUpdateReplication() throws Exception { + serverMockClient.when( + request() + .withMethod("PUT") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + instanceInfo.getAppName() + '/' + instanceInfo.getId() + "/status") + ).respond( + response().withStatusCode(200) + ); + + EurekaHttpResponse response = replicationClient.statusUpdate(instanceInfo.getAppName(), instanceInfo.getId(), InstanceStatus.DOWN, instanceInfo); + assertThat(response.getStatusCode(), is(equalTo(200))); + } + + @Test + public void testDeleteStatusOverrideReplication() throws Exception { + serverMockClient.when( + request() + .withMethod("DELETE") + .withHeader(header(PeerEurekaNode.HEADER_REPLICATION, "true")) + .withPath("/eureka/v2/apps/" + instanceInfo.getAppName() + '/' + instanceInfo.getId() + "/status") + ).respond( + response().withStatusCode(204) + ); + + EurekaHttpResponse response = replicationClient.deleteStatusOverride(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo); + assertThat(response.getStatusCode(), is(equalTo(204))); + } + + private static byte[] toGzippedJson(InstanceInfo remoteInfo) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + GZIPOutputStream gos = new GZIPOutputStream(bos); + EurekaJacksonCodec.getInstance().writeTo(remoteInfo, gos); + gos.flush(); + return bos.toByteArray(); + } +} \ No newline at end of file diff --git a/eureka-core/src/main/java/com/netflix/eureka/EurekaBootStrap.java b/eureka-core/src/main/java/com/netflix/eureka/EurekaBootStrap.java index f74805b74..8196d9156 100644 --- a/eureka-core/src/main/java/com/netflix/eureka/EurekaBootStrap.java +++ b/eureka-core/src/main/java/com/netflix/eureka/EurekaBootStrap.java @@ -32,7 +32,6 @@ import com.netflix.config.DeploymentContext; import com.netflix.discovery.DefaultEurekaClientConfig; import com.netflix.discovery.DiscoveryClient; -import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.converters.JsonXStream; import com.netflix.discovery.converters.XmlXStream; @@ -82,6 +81,12 @@ public class EurekaBootStrap implements ServletContextListener { protected volatile EurekaServerContext serverContext; protected volatile AwsBinder awsBinder; + + private DiscoveryClient discoveryClient; + + protected void setDiscoveryClient(DiscoveryClient discoveryClient) { + this.discoveryClient = discoveryClient; + } /** * Initializes Eureka, including syncing up with other Eureka peers and publishing the registry. @@ -133,42 +138,49 @@ protected void initEurekaServerContext() throws Exception { JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); - EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext()) - ? new CloudInstanceConfig() - : new MyDataCenterInstanceConfig(); - logger.info("Initializing the eureka client..."); + logger.info(eurekaServerConfig.getJsonCodecName()); ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig); - ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager( - instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()); - - EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); - EurekaClient eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); + ApplicationInfoManager applicationInfoManager = null; + + if (discoveryClient == null) { + EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext()) + ? new CloudInstanceConfig() + : new MyDataCenterInstanceConfig(); + + applicationInfoManager = new ApplicationInfoManager( + instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()); + + EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); + discoveryClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); + } else { + applicationInfoManager = discoveryClient.getApplicationInfoManager(); + } PeerAwareInstanceRegistry registry; if (isAws(applicationInfoManager.getInfo())) { registry = new AwsInstanceRegistry( eurekaServerConfig, - eurekaClientConfig, + discoveryClient.getEurekaClientConfig(), serverCodecs, - eurekaClient + discoveryClient ); - awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClientConfig, registry, applicationInfoManager); + awsBinder = new AwsBinderDelegate(eurekaServerConfig, discoveryClient.getEurekaClientConfig(), registry, applicationInfoManager); awsBinder.start(); } else { registry = new PeerAwareInstanceRegistryImpl( eurekaServerConfig, - eurekaClientConfig, + discoveryClient.getEurekaClientConfig(), serverCodecs, - eurekaClient + discoveryClient ); } - PeerEurekaNodes peerEurekaNodes = new PeerEurekaNodes( + PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( registry, eurekaServerConfig, - eurekaClientConfig, + discoveryClient.getEurekaClientConfig(), serverCodecs, applicationInfoManager ); @@ -193,6 +205,18 @@ protected void initEurekaServerContext() throws Exception { // Register all monitoring statistics. EurekaMonitors.registerAllStats(); } + + protected PeerEurekaNodes getPeerEurekaNodes(PeerAwareInstanceRegistry registry, EurekaServerConfig eurekaServerConfig, EurekaClientConfig eurekaClientConfig, ServerCodecs serverCodecs, ApplicationInfoManager applicationInfoManager) { + PeerEurekaNodes peerEurekaNodes = new PeerEurekaNodes( + registry, + eurekaServerConfig, + eurekaClientConfig, + serverCodecs, + applicationInfoManager + ); + + return peerEurekaNodes; + } /** * Handles Eureka cleanup, including shutting down all monitors and yielding all EIPs. diff --git a/eureka-core/src/main/java/com/netflix/eureka/cluster/PeerEurekaNodes.java b/eureka-core/src/main/java/com/netflix/eureka/cluster/PeerEurekaNodes.java index 2d34c2179..ec912bbef 100644 --- a/eureka-core/src/main/java/com/netflix/eureka/cluster/PeerEurekaNodes.java +++ b/eureka-core/src/main/java/com/netflix/eureka/cluster/PeerEurekaNodes.java @@ -35,10 +35,10 @@ public class PeerEurekaNodes { private static final Logger logger = LoggerFactory.getLogger(PeerEurekaNodes.class); - private final PeerAwareInstanceRegistry registry; - private final EurekaServerConfig serverConfig; - private final EurekaClientConfig clientConfig; - private final ServerCodecs serverCodecs; + protected final PeerAwareInstanceRegistry registry; + protected final EurekaServerConfig serverConfig; + protected final EurekaClientConfig clientConfig; + protected final ServerCodecs serverCodecs; private final ApplicationInfoManager applicationInfoManager; private volatile List peerEurekaNodes = Collections.emptyList(); diff --git a/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodeTest.java b/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodeTest.java index 2e2e973f3..70cd32888 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodeTest.java +++ b/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodeTest.java @@ -5,6 +5,7 @@ import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.discovery.shared.transport.ClusterSampleData; import com.netflix.eureka.EurekaServerConfig; import com.netflix.eureka.registry.PeerAwareInstanceRegistry; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.Action; @@ -17,8 +18,6 @@ import org.junit.Before; import org.junit.Test; -import static com.netflix.eureka.cluster.ClusterSampleData.RETRY_SLEEP_TIME_MS; -import static com.netflix.eureka.cluster.ClusterSampleData.SERVER_UNAVAILABLE_SLEEP_TIME_MS; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -144,8 +143,8 @@ private PeerEurekaNode createPeerEurekaNode() { config, BATCH_SIZE, MAX_BATCHING_DELAY_MS, - RETRY_SLEEP_TIME_MS, - SERVER_UNAVAILABLE_SLEEP_TIME_MS + ClusterSampleData.RETRY_SLEEP_TIME_MS, + ClusterSampleData.SERVER_UNAVAILABLE_SLEEP_TIME_MS ); return peerEurekaNode; } diff --git a/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodesTest.java b/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodesTest.java index 410560ac7..11aceee78 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodesTest.java +++ b/eureka-core/src/test/java/com/netflix/eureka/cluster/PeerEurekaNodesTest.java @@ -10,6 +10,7 @@ import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.discovery.DefaultEurekaClientConfig; +import com.netflix.discovery.shared.transport.ClusterSampleData; import com.netflix.eureka.EurekaServerConfig; import com.netflix.eureka.registry.PeerAwareInstanceRegistry; import com.netflix.eureka.resources.DefaultServerCodecs; diff --git a/eureka-core/src/test/java/com/netflix/eureka/cluster/protocol/JacksonEncodingTest.java b/eureka-core/src/test/java/com/netflix/eureka/cluster/protocol/JacksonEncodingTest.java index 8bd70b409..5843b65b4 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/cluster/protocol/JacksonEncodingTest.java +++ b/eureka-core/src/test/java/com/netflix/eureka/cluster/protocol/JacksonEncodingTest.java @@ -1,7 +1,8 @@ package com.netflix.eureka.cluster.protocol; import com.netflix.discovery.converters.EurekaJacksonCodec; -import com.netflix.eureka.cluster.ClusterSampleData; +import com.netflix.discovery.shared.transport.ClusterSampleData; + import org.junit.Test; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/eureka-core/src/test/java/com/netflix/eureka/resources/PeerReplicationResourceTest.java b/eureka-core/src/test/java/com/netflix/eureka/resources/PeerReplicationResourceTest.java index f0b8c2e3c..87a24cbe6 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/resources/PeerReplicationResourceTest.java +++ b/eureka-core/src/test/java/com/netflix/eureka/resources/PeerReplicationResourceTest.java @@ -3,16 +3,16 @@ import javax.ws.rs.core.Response; import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.shared.transport.ClusterSampleData; import com.netflix.eureka.EurekaServerContext; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.Action; -import com.netflix.eureka.cluster.ClusterSampleData; 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 org.junit.Test; -import static com.netflix.eureka.cluster.ClusterSampleData.newReplicationInstanceOf; +import static com.netflix.discovery.shared.transport.ClusterSampleData.newReplicationInstanceOf; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; diff --git a/eureka-test-utils/src/main/java/com/netflix/discovery/junit/resource/DiscoveryClientResource.java b/eureka-test-utils/src/main/java/com/netflix/discovery/junit/resource/DiscoveryClientResource.java index 5414e975f..a89b4d6f3 100644 --- a/eureka-test-utils/src/main/java/com/netflix/discovery/junit/resource/DiscoveryClientResource.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/junit/resource/DiscoveryClientResource.java @@ -11,7 +11,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import com.google.common.base.Preconditions; import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.DataCenterInfo; @@ -23,11 +22,11 @@ import com.netflix.discovery.CacheRefreshedEvent; import com.netflix.discovery.DefaultEurekaClientConfig; import com.netflix.discovery.DiscoveryClient; -import com.netflix.discovery.DiscoveryClient.DiscoveryClientOptionalArgs; import com.netflix.discovery.DiscoveryManager; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.shared.transport.SimpleEurekaHttpServer; +import com.netflix.discovery.shared.transport.jersey.DiscoveryClientOptionalArgs; import com.netflix.eventbus.impl.EventBusImpl; import com.netflix.eventbus.spi.EventBus; import com.netflix.eventbus.spi.InvalidSubscriberException; diff --git a/eureka-core/src/test/java/com/netflix/eureka/cluster/ClusterSampleData.java b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java similarity index 98% rename from eureka-core/src/test/java/com/netflix/eureka/cluster/ClusterSampleData.java rename to eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java index 3a9cb71a6..0b2bd2b2b 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/cluster/ClusterSampleData.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java @@ -1,4 +1,4 @@ -package com.netflix.eureka.cluster; +package com.netflix.discovery.shared.transport; import java.util.Iterator; diff --git a/settings.gradle b/settings.gradle index 827ada7ad..a001300d1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ include 'eureka-client', 'eureka-server', 'eureka-server-governator', 'eureka-core', + 'eureka-core-jersey2', 'eureka-resources', 'eureka-examples', 'eureka-test-utils'