package com.netflix.eureka.cluster;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.endpoint.EndpointUtils;
import com.netflix.discovery.shared.Application;
import com.netflix.eureka.EurekaServerConfig;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import com.netflix.eureka.resources.ServerCodecs;
import com.netflix.eureka.transport.JerseyReplicationClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class to manage lifecycle of a collection of {@link PeerEurekaNode}s.
*
* @author Tomasz Bak
*/
@Singleton
public class PeerEurekaNodes {
private static final Logger logger = LoggerFactory.getLogger(PeerEurekaNodes.class);
protected final PeerAwareInstanceRegistry registry;
protected final EurekaServerConfig serverConfig;
protected final EurekaClientConfig clientConfig;
protected final ServerCodecs serverCodecs;
private final ApplicationInfoManager applicationInfoManager;
private volatile List<PeerEurekaNode> peerEurekaNodes = Collections.emptyList();
private volatile Set<String> peerEurekaNodeUrls = Collections.emptySet();
private ScheduledExecutorService taskExecutor;
@Inject
public PeerEurekaNodes(
PeerAwareInstanceRegistry registry,
EurekaServerConfig serverConfig,
EurekaClientConfig clientConfig,
ServerCodecs serverCodecs,
ApplicationInfoManager applicationInfoManager) {
this.registry = registry;
this.serverConfig = serverConfig;
this.clientConfig = clientConfig;
this.serverCodecs = serverCodecs;
this.applicationInfoManager = applicationInfoManager;
}
public List<PeerEurekaNode> getPeerNodesView() {
return Collections.unmodifiableList(peerEurekaNodes);
}
public List<PeerEurekaNode> getPeerEurekaNodes() {
return peerEurekaNodes;
}
public int getMinNumberOfAvailablePeers() {
return serverConfig.getHealthStatusMinNumberOfAvailablePeers();
}
public void start() {
taskExecutor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
thread.setDaemon(true);
return thread;
}
}
);
try {
updatePeerEurekaNodes(resolvePeerUrls());
Runnable peersUpdateTask = new Runnable() {
@Override
public void run() {
try {
updatePeerEurekaNodes(resolvePeerUrls());
} catch (Throwable e) {
logger.error("Cannot update the replica Nodes", e);
}
}
};
taskExecutor.scheduleWithFixedDelay(
peersUpdateTask,
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
TimeUnit.MILLISECONDS
);
} catch (Exception e) {
throw new IllegalStateException(e);
}
for (PeerEurekaNode node : peerEurekaNodes) {
logger.info("Replica node URL: " + node.getServiceUrl());
}
}
public void shutdown() {
taskExecutor.shutdown();
List<PeerEurekaNode> toRemove = this.peerEurekaNodes;
this.peerEurekaNodes = Collections.emptyList();
this.peerEurekaNodeUrls = Collections.emptySet();
for (PeerEurekaNode node : toRemove) {
node.shutDown();
}
}
/**
* Resolve peer URLs.
*
* @return peer URLs with node's own URL filtered out
*/
protected List<String> resolvePeerUrls() {
InstanceInfo myInfo = applicationInfoManager.getInfo();
String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo);
List<String> replicaUrls = EndpointUtils
.getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));
int idx = 0;
while (idx < replicaUrls.size()) {
if (isThisMyUrl(replicaUrls.get(idx))) {
replicaUrls.remove(idx);
} else {
idx++;
}
}
return replicaUrls;
}
/**
* Given new set of replica URLs, destroy {@link PeerEurekaNode}s no longer available, and
* create new ones.
*
* @param newPeerUrls peer node URLs; this collection should have local node's URL filtered out
*/
protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
if (newPeerUrls.isEmpty()) {
logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
return;
}
Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
toShutdown.removeAll(newPeerUrls);
Set<String> toAdd = new HashSet<>(newPeerUrls);
toAdd.removeAll(peerEurekaNodeUrls);
if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
return;
}
// Remove peers no long available
List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);
if (!toShutdown.isEmpty()) {
logger.info("Removing no longer available peer nodes {}", toShutdown);
int i = 0;
while (i < newNodeList.size()) {
PeerEurekaNode eurekaNode = newNodeList.get(i);
if (toShutdown.contains(eurekaNode.getServiceUrl())) {
newNodeList.remove(i);
eurekaNode.shutDown();
} else {
i++;
}
}
}
// Add new peers
if (!toAdd.isEmpty()) {
logger.info("Adding new peer nodes {}", toAdd);
for (String peerUrl : toAdd) {
newNodeList.add(createPeerEurekaNode(peerUrl));
}
}
this.peerEurekaNodes = newNodeList;
this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
}
protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) {
HttpReplicationClient replicationClient = JerseyReplicationClient.createReplicationClient(serverConfig, serverCodecs, peerEurekaNodeUrl);
String targetHost = hostFromUrl(peerEurekaNodeUrl);
if (targetHost == null) {
targetHost = "host";
}
return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl, replicationClient, serverConfig);
}
/**
* @deprecated 2016-06-27 use instance version of {@link #isThisMyUrl(String)}
*
* Checks if the given service url contains the current host which is trying
* to replicate. Only after the EIP binding is done the host has a chance to
* identify itself in the list of replica nodes and needs to take itself out
* of replication traffic.
*
* @param url the service url of the replica node that the check is made.
* @return true, if the url represents the current node which is trying to
* replicate, false otherwise.
*/
public static boolean isThisMe(String url) {
InstanceInfo myInfo = ApplicationInfoManager.getInstance().getInfo();
String hostName = hostFromUrl(url);
return hostName != null && hostName.equals(myInfo.getHostName());
}
/**
* Checks if the given service url contains the current host which is trying
* to replicate. Only after the EIP binding is done the host has a chance to
* identify itself in the list of replica nodes and needs to take itself out
* of replication traffic.
*
* @param url the service url of the replica node that the check is made.
* @return true, if the url represents the current node which is trying to
* replicate, false otherwise.
*/
public boolean isThisMyUrl(String url) {
return isInstanceURL(url, applicationInfoManager.getInfo());
}
/**
* Checks if the given service url matches the supplied instance
*
* @param url the service url of the replica node that the check is made.
* @param instance the instance to check the service url against
* @return true, if the url represents the supplied instance, false otherwise.
*/
public boolean isInstanceURL(String url, InstanceInfo instance) {
String hostName = hostFromUrl(url);
String myInfoComparator = instance.getHostName();
if (clientConfig.getTransportConfig().applicationsResolverUseIp()) {
myInfoComparator = instance.getIPAddr();
}
return hostName != null && hostName.equals(myInfoComparator);
}
public static String hostFromUrl(String url) {
URI uri;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
logger.warn("Cannot parse service URI " + url, e);
return null;
}
return uri.getHost();
}
}