package com.netflix.discovery.junit.resource; import javax.ws.rs.core.UriBuilder; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; 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; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.LeaseInfo; import com.netflix.appinfo.MyDataCenterInstanceConfig; import com.netflix.config.ConfigurationManager; import com.netflix.discovery.CacheRefreshedEvent; import com.netflix.discovery.DefaultEurekaClientConfig; import com.netflix.discovery.DiscoveryClient; 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.Jersey1DiscoveryClientOptionalArgs; import com.netflix.eventbus.impl.EventBusImpl; import com.netflix.eventbus.spi.EventBus; import com.netflix.eventbus.spi.InvalidSubscriberException; import com.netflix.eventbus.spi.Subscribe; import org.junit.rules.ExternalResource; /** * JUnit rule for discovery client + collection of static methods for setting it up. */ public class DiscoveryClientResource extends ExternalResource { public static final String REMOTE_REGION = "myregion"; public static final String REMOTE_ZONE = "myzone"; public static final int CLIENT_REFRESH_RATE = 10; public static final String EUREKA_TEST_NAMESPACE = "eurekaTestNamespace."; private static final Set<String> SYSTEM_PROPERTY_TRACKER = new HashSet<>(); private final boolean registrationEnabled; private final boolean registryFetchEnabled; private final InstanceInfo instance; private final SimpleEurekaHttpServer eurekaHttpServer; private final Callable<Integer> portResolverCallable; private final List<String> remoteRegions; private final String vipFetch; private final String userName; private final String password; private EventBus eventBus; private ApplicationInfoManager applicationManager; private EurekaClient client; private final List<DiscoveryClientResource> forkedDiscoveryClientResources = new ArrayList<>(); private ApplicationInfoManager applicationInfoManager; DiscoveryClientResource(DiscoveryClientRuleBuilder builder) { this.registrationEnabled = builder.registrationEnabled; this.registryFetchEnabled = builder.registryFetchEnabled; this.portResolverCallable = builder.portResolverCallable; this.eurekaHttpServer = builder.eurekaHttpServer; this.instance = builder.instance; this.remoteRegions = builder.remoteRegions; this.vipFetch = builder.vipFetch; this.userName = builder.userName; this.password = builder.password; } public InstanceInfo getMyInstanceInfo() { return createApplicationManager().getInfo(); } public EventBus getEventBus() { if (client == null) { getClient(); // Lazy initialization } return eventBus; } public ApplicationInfoManager getApplicationInfoManager() { return applicationInfoManager; } public EurekaClient getClient() { if (client == null) { try { applicationInfoManager = createApplicationManager(); EurekaClientConfig clientConfig = createEurekaClientConfig(); Jersey1DiscoveryClientOptionalArgs optionalArgs = new Jersey1DiscoveryClientOptionalArgs(); eventBus = new EventBusImpl(); optionalArgs.setEventBus(eventBus); client = new DiscoveryClient(applicationInfoManager, clientConfig, optionalArgs); } catch (Exception e) { throw new RuntimeException(e); } } return client; } public boolean awaitCacheUpdate(long timeout, TimeUnit unit) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); Object eventListener = new Object() { @Subscribe public void consume(CacheRefreshedEvent event) { latch.countDown(); } }; try { getEventBus().registerSubscriber(eventListener); } catch (InvalidSubscriberException e) { throw new IllegalStateException("Unexpected error during subscriber registration", e); } try { return latch.await(timeout, unit); } finally { getEventBus().unregisterSubscriber(eventListener); } } private ApplicationInfoManager createApplicationManager() { if (applicationManager == null) { EurekaInstanceConfig instanceConfig = new MyDataCenterInstanceConfig(EUREKA_TEST_NAMESPACE) { @Override public String getAppname() { return "discoveryClientTest"; } @Override public int getLeaseRenewalIntervalInSeconds() { return 1; } }; applicationManager = new ApplicationInfoManager(instanceConfig); } return applicationManager; } private EurekaClientConfig createEurekaClientConfig() throws Exception { // Cluster connectivity URI serviceURI; if (portResolverCallable != null) { serviceURI = new URI("http://localhost:" + portResolverCallable.call() + "/eureka/v2/"); } else if (eurekaHttpServer != null) { serviceURI = eurekaHttpServer.getServiceURI(); } else { throw new IllegalStateException("Either port or EurekaHttpServer must be configured"); } if (userName != null) { serviceURI = UriBuilder.fromUri(serviceURI).userInfo(userName + ':' + password).build(); } bindProperty(EUREKA_TEST_NAMESPACE + "serviceUrl.default", serviceURI.toString()); if (remoteRegions != null && !remoteRegions.isEmpty()) { StringBuilder regions = new StringBuilder(); for (String region : remoteRegions) { regions.append(',').append(region); } bindProperty(EUREKA_TEST_NAMESPACE + "fetchRemoteRegionsRegistry", regions.substring(1)); } // Registration bindProperty(EUREKA_TEST_NAMESPACE + "registration.enabled", Boolean.toString(registrationEnabled)); bindProperty(EUREKA_TEST_NAMESPACE + "appinfo.initial.replicate.time", Integer.toString(0)); bindProperty(EUREKA_TEST_NAMESPACE + "appinfo.replicate.interval", Integer.toString(1)); // Registry fetch bindProperty(EUREKA_TEST_NAMESPACE + "shouldFetchRegistry", Boolean.toString(registryFetchEnabled)); bindProperty(EUREKA_TEST_NAMESPACE + "client.refresh.interval", Integer.toString(1)); if (vipFetch != null) { bindProperty(EUREKA_TEST_NAMESPACE + "registryRefreshSingleVipAddress", vipFetch); } return new DefaultEurekaClientConfig(EUREKA_TEST_NAMESPACE); } @Override protected void after() { if (client != null) { client.shutdown(); } for (DiscoveryClientResource resource : forkedDiscoveryClientResources) { resource.after(); } for (String property : SYSTEM_PROPERTY_TRACKER) { ConfigurationManager.getConfigInstance().clearProperty(property); } clearDiscoveryClientConfig(); } public DiscoveryClientRuleBuilder fork() { DiscoveryClientRuleBuilder builder = new DiscoveryClientRuleBuilder() { @Override public DiscoveryClientResource build() { DiscoveryClientResource clientResource = super.build(); try { clientResource.before(); } catch (Throwable e) { throw new IllegalStateException("Unexpected error during forking the client resource", e); } forkedDiscoveryClientResources.add(clientResource); return clientResource; } }; return builder.withInstanceInfo(instance) .connectWith(eurekaHttpServer) .withPortResolver(portResolverCallable) .withRegistration(registrationEnabled) .withRegistryFetch(registryFetchEnabled) .withRemoteRegions(remoteRegions.toArray(new String[remoteRegions.size()])); } public static DiscoveryClientRuleBuilder newBuilder() { return new DiscoveryClientRuleBuilder(); } public static void setupDiscoveryClientConfig(int serverPort, String path) { ConfigurationManager.getConfigInstance().setProperty("eureka.shouldFetchRegistry", "true"); ConfigurationManager.getConfigInstance().setProperty("eureka.responseCacheAutoExpirationInSeconds", "10"); ConfigurationManager.getConfigInstance().setProperty("eureka.client.refresh.interval", CLIENT_REFRESH_RATE); ConfigurationManager.getConfigInstance().setProperty("eureka.registration.enabled", "false"); ConfigurationManager.getConfigInstance().setProperty("eureka.fetchRemoteRegionsRegistry", REMOTE_REGION); ConfigurationManager.getConfigInstance().setProperty("eureka.myregion.availabilityZones", REMOTE_ZONE); ConfigurationManager.getConfigInstance().setProperty("eureka.serviceUrl.default", "http://localhost:" + serverPort + path); } public static void clearDiscoveryClientConfig() { ConfigurationManager.getConfigInstance().clearProperty("eureka.client.refresh.interval"); ConfigurationManager.getConfigInstance().clearProperty("eureka.registration.enabled"); ConfigurationManager.getConfigInstance().clearProperty("eureka.fetchRemoteRegionsRegistry"); ConfigurationManager.getConfigInstance().clearProperty("eureka.myregion.availabilityZones"); ConfigurationManager.getConfigInstance().clearProperty("eureka.serviceUrl.default"); } public static EurekaClient setupDiscoveryClient(InstanceInfo clientInstanceInfo) { DefaultEurekaClientConfig config = new DefaultEurekaClientConfig(); // setup config in advance, used in initialize converter ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(new MyDataCenterInstanceConfig(), clientInstanceInfo); DiscoveryManager.getInstance().setEurekaClientConfig(config); EurekaClient client = new DiscoveryClient(applicationInfoManager, config); return client; } public static EurekaClient setupInjector(InstanceInfo clientInstanceInfo) { DefaultEurekaClientConfig config = new DefaultEurekaClientConfig(); // setup config in advance, used in initialize converter DiscoveryManager.getInstance().setEurekaClientConfig(config); EurekaClient client = new DiscoveryClient(clientInstanceInfo, config); ApplicationInfoManager.getInstance().initComponent(new MyDataCenterInstanceConfig()); return client; } public static InstanceInfo.Builder newInstanceInfoBuilder(int renewalIntervalInSecs) { InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(); builder.setIPAddr("10.10.101.00"); builder.setHostName("Hosttt"); builder.setAppName("EurekaTestApp-" + UUID.randomUUID()); builder.setDataCenterInfo(new DataCenterInfo() { @Override public Name getName() { return Name.MyOwn; } }); builder.setLeaseInfo(LeaseInfo.Builder.newBuilder().setRenewalIntervalInSecs(renewalIntervalInSecs).build()); return builder; } private static void bindProperty(String propertyName, String value) { SYSTEM_PROPERTY_TRACKER.add(propertyName); ConfigurationManager.getConfigInstance().setProperty(propertyName, value); } public static class DiscoveryClientRuleBuilder { private boolean registrationEnabled; private boolean registryFetchEnabled; private Callable<Integer> portResolverCallable; private InstanceInfo instance; private SimpleEurekaHttpServer eurekaHttpServer; private List<String> remoteRegions; private String vipFetch; private String userName; private String password; public DiscoveryClientRuleBuilder withInstanceInfo(InstanceInfo instance) { this.instance = instance; return this; } public DiscoveryClientRuleBuilder withRegistration(boolean enabled) { this.registrationEnabled = enabled; return this; } public DiscoveryClientRuleBuilder withRegistryFetch(boolean enabled) { this.registryFetchEnabled = enabled; return this; } public DiscoveryClientRuleBuilder withPortResolver(Callable<Integer> portResolverCallable) { this.portResolverCallable = portResolverCallable; return this; } public DiscoveryClientRuleBuilder connectWith(SimpleEurekaHttpServer eurekaHttpServer) { this.eurekaHttpServer = eurekaHttpServer; return this; } public DiscoveryClientRuleBuilder withRemoteRegions(String... remoteRegions) { if (this.remoteRegions == null) { this.remoteRegions = new ArrayList<>(); } Collections.addAll(this.remoteRegions, remoteRegions); return this; } public DiscoveryClientRuleBuilder withVipFetch(String vipFetch) { this.vipFetch = vipFetch; return this; } public DiscoveryClientRuleBuilder basicAuthentication(String userName, String password) { Preconditions.checkNotNull(userName, "HTTP basic authentication user name is null"); Preconditions.checkNotNull(password, "HTTP basic authentication password is null"); this.userName = userName; this.password = password; return this; } public DiscoveryClientResource build() { return new DiscoveryClientResource(this); } } }