com.fasterxml.clustermate.client.StoreClientBootstrapper Maven / Gradle / Ivy
Show all versions of clustermate-client Show documentation
package com.fasterxml.clustermate.client;
import java.io.IOException;
import java.util.*;
import com.fasterxml.storemate.shared.IpAndPort;
import com.fasterxml.clustermate.api.ClusterStatusAccessor;
import com.fasterxml.clustermate.api.EntryKey;
import com.fasterxml.clustermate.api.NodeState;
import com.fasterxml.clustermate.api.msg.ClusterStatusMessage;
import com.fasterxml.clustermate.client.cluster.ClusterViewByClientImpl;
import com.fasterxml.clustermate.json.ClusterMessageConverter;
import com.fasterxml.clustermate.std.JdkClusterStatusAccessor;
/**
* Helper class used for constructing and initializing a
* {@link StoreClient} instance.
*/
public abstract class StoreClientBootstrapper<
K extends EntryKey,
CONFIG extends StoreClientConfig,
STORE extends StoreClient,
BOOTSTRAPPER extends StoreClientBootstrapper
>
extends Loggable
{
/**
* Let's keep initial timeouts relatively low, since we can usually
* try to go through multiple server nodes to get response quickly.
*/
public final static long BOOTSTRAP_TIMEOUT_MSECS = 2000L;
/*
/**********************************************************************
/* Helper objects
/**********************************************************************
*/
protected final CONFIG _config;
/**
* Low-level library we use for making network requests.
*/
protected final NetworkClient _httpClient;
/**
* Set of server nodes used for bootstrapping; we need at least
* one to be able to locate others.
*/
protected final Set _nodes = new LinkedHashSet();
/**
* Object used to access Node State information, needed to construct
* view of the cluster.
*/
protected ClusterStatusAccessor _accessor;
/*
/**********************************************************************
/* Construction
/**********************************************************************
*/
public StoreClientBootstrapper(CONFIG config, NetworkClient hc)
{
super(StoreClientBootstrapper.class);
_config = config;
_httpClient = hc;
}
/*
/**********************************************************************
/* Builder initialization
/**********************************************************************
*/
@SuppressWarnings("unchecked")
public BOOTSTRAPPER addNodes(IpAndPort... nodes)
{
for (IpAndPort node : nodes) {
_nodes.add(node);
}
return (BOOTSTRAPPER) this;
}
@SuppressWarnings("unchecked")
public BOOTSTRAPPER addNodes(String... nodes)
{
for (String node : nodes) {
_nodes.add(new IpAndPort(node));
}
return (BOOTSTRAPPER) this;
}
@SuppressWarnings("unchecked")
public BOOTSTRAPPER addNode(IpAndPort node) {
_nodes.add(node);
return (BOOTSTRAPPER) this;
}
public BOOTSTRAPPER addNode(String node) {
return addNode(new IpAndPort(node));
}
/*
/**********************************************************************
/* Client bootstrapping:
/**********************************************************************
*/
/**
* Method to call to construct {@link StoreClient}, and ensure that it
* can serve some requests: basically it will be able to contact at
* least a single server node and thus handle at least part of the
* key space.
*
* If initialization can not be completed within
*
* @param maxWaitSecs Maximum time wait, in seconds, if more than 0; if
* 0 or negative, will try to initialize indefinitely
*
* @throws IllegalStateException If configuration is incomplete; for example
* if no server nodes have been configured
*/
public STORE buildAndInitMinimally(int maxWaitSecs)
throws IOException
{
return _buildAndInit(maxWaitSecs, false);
}
/**
* Method to call to construct {@link StoreClient}, and ensure that it
* can serve some requests: basically it will be able to contact at
* least a single server node and thus handle at least part of the
* key space.
*
* @throws IllegalStateException If configuration is incomplete; for example
* if no server nodes have been configured
*/
public STORE buildAndInitCompletely(int maxWaitSecs)
throws IOException
{
return _buildAndInit(maxWaitSecs, true);
}
/**
* Method called to verify that builder information is minimally valid,
* to give early error information if possible.
*/
protected void _verifySetup()
{
// First: check that we have at least one configured node:
final int nodeCount = _nodes.size();
if (nodeCount == 0) {
throw new IllegalStateException("No server nodes defined for client, can not build");
}
// Second: try resolving IPs...
Iterator it = _nodes.iterator();
while (it.hasNext()) {
IpAndPort ip = it.next();
try {
ip.getIP();
} catch (Exception e) {
logError("Failed to resolve end point '"+ip.toString()+"', skipping. Problem: "+e.getMessage());
it.remove();
}
}
// Still some left?
if (_nodes.isEmpty()) {
throw new IllegalStateException("Failed to resolve any of configured node definitions ("
+nodeCount+"): can not build");
}
}
protected STORE _buildAndInit(int maxWaitSecs, boolean fullInit)
throws IOException
{
_accessor = new JdkClusterStatusAccessor(new ClusterMessageConverter(_config.getJsonMapper()),
_config.getBasePath(), _config.getPathStrategy());
_verifySetup();
final long startTime = System.currentTimeMillis();
final long waitUntil = (maxWaitSecs <= 0) ? Long.MAX_VALUE : (startTime + 1000 * maxWaitSecs);
// We'll keep track of other seed nodes:
ArrayList ips = new ArrayList(_nodes);
ClusterViewByClientImpl clusterView = _getInitialState(_config, ips, waitUntil);
// Nothing found before timeout? Bummer!
if (clusterView == null) {
// important: need to close client, otherwise exit from JVM won't work too well
_accessor = null;
throw new IllegalStateException("Failed to contact any of servers for Cluster Status: can not build client");
}
// This is the minimal state. Do we need more?
boolean fullyAvailable = clusterView.isFullyAvailable();
if (fullyAvailable) {
if (isInfoEnabled()) {
logInfo("Cluster information partially initialized; fully available: "+fullyAvailable);
}
} else if (fullInit) {
while (true) {
if (isInfoEnabled()) {
logInfo("Cluster information partially initialized, but only part of keyspace covered:"
+"need to continue initialization (have "+ips.size()+" nodes to check)");
}
// May need to loop for a while
long roundStart = System.currentTimeMillis();
if (_updateInitialState(ips, waitUntil, clusterView)) {
if (clusterView.isFullyAvailable()) {
break;
}
if (ips.isEmpty()) {
throw new IllegalStateException("Unable to fully initialize Cluster: all seed nodes handled; keyspace coverage incomplete");
}
}
// If we failed first time around, let's wait a bit...
long now = System.currentTimeMillis();
if (now > waitUntil) {
throw new IllegalStateException("Unable to fully initialize Cluster: keyspace coverage incomplete, timed out");
}
long timeTaken = now - roundStart;
if (timeTaken < 3000L) { // if we had string of failures, wait a bit
try {
Thread.sleep(3000L - timeTaken);
} catch (InterruptedException e) {
throw new IOException(e.getMessage(), e);
}
}
}
if (isInfoEnabled()) {
logInfo("Cluster information completely initialized, the whole keyspace covered!");
}
}
return _buildClient(_config, _accessor, clusterView, _httpClient);
}
protected abstract STORE _buildClient(CONFIG config, ClusterStatusAccessor accessor,
ClusterViewByClientImpl clusterView, NetworkClient client);
/**
* Method called to find information from the first available seed
* server node.
*/
protected ClusterViewByClientImpl _getInitialState(CONFIG config,
Collection nodes,
long waitUntil) throws IOException
{
// First things first: must get one valid response first:
long roundStart;
while ((roundStart = System.currentTimeMillis()) < waitUntil) {
Iterator it = nodes.iterator();
while (it.hasNext()) {
IpAndPort ip = it.next();
final long requestTime = System.currentTimeMillis();
long maxTimeout = waitUntil - requestTime;
try {
ClusterStatusMessage resp = _accessor.getClusterStatus(ip,
Math.min(maxTimeout, BOOTSTRAP_TIMEOUT_MSECS));
if (resp == null) {
continue;
}
it.remove(); // remove from bootstrap list
NodeState local = resp.local;
ClusterViewByClientImpl clusterView = new ClusterViewByClientImpl(
config, _httpClient, local.totalRange().getKeyspace());
clusterView.updateDirectState(ip, local,
requestTime, System.currentTimeMillis(), resp.creationTime);
for (NodeState stateSec : resp.remote) {
clusterView.updateIndirectState(ip, stateSec);
}
return clusterView;
} catch (RuntimeException e) { // usually more severe ones, NPE etc
logError(e, "Internal error with cluster state call (IP "+ip+"): "
+"("+e.getClass().getName()+") "+e.getMessage());
} catch (Exception e) {
logWarn("Initial cluster state call (IP "+ip+") failed: ("+e.getClass().getName()+") "
+e.getMessage());
}
}
// If we failed first time around, let's wait a bit...
long timeTaken = System.currentTimeMillis() - roundStart;
if (timeTaken < 1000L) { // if we had string of failures, wait a bit
try {
Thread.sleep(1000L - timeTaken);
} catch (InterruptedException e) {
throw new IOException(e.getMessage(), e);
}
}
}
return null;
}
/**
* Method that will try to fetch and resolve cluster state from one of
* given nodes, to complete initialization wrt key space coverage
*
* @return True if we managed to resolve one more node; false if not.
*/
protected boolean _updateInitialState(Collection nodes,
long waitUntil, ClusterViewByClientImpl clusterView) throws IOException
{
Iterator it = nodes.iterator();
while (it.hasNext()) {
IpAndPort ip = it.next();
final long requestTime = System.currentTimeMillis();
long maxTimeout = waitUntil - requestTime;
try {
ClusterStatusMessage resp = _accessor.getClusterStatus(ip,
Math.min(maxTimeout, BOOTSTRAP_TIMEOUT_MSECS));
if (resp == null) {
continue;
}
it.remove(); // remove from bootstrap list
clusterView.updateDirectState(ip, resp.local,
requestTime, System.currentTimeMillis(), resp.clusterLastUpdated);
for (NodeState stateSec : resp.remote) {
clusterView.updateIndirectState(ip, stateSec);
}
return true;
} catch (Exception e) {
logWarn(e, "Secondary cluster state init call (IP "+ip+") failed: "+e.getMessage());
}
}
return false;
}
}