com.datastax.driver.core.Host Maven / Gradle / Ivy
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import com.google.common.util.concurrent.ListenableFuture;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Cassandra node.
*
* This class keeps the information the driver maintain on a given Cassandra node.
*/
public class Host {
private static final Logger logger = LoggerFactory.getLogger(Host.class);
static final Logger statesLogger = LoggerFactory.getLogger(Host.class.getName() + ".STATES");
// The address we'll use to connect to the node
private final EndPoint endPoint;
// The broadcast RPC address, as reported in system tables.
// Note that, unlike previous versions of the driver, this address is NOT TRANSLATED.
private volatile InetSocketAddress broadcastRpcAddress;
// The broadcast_address as known by Cassandra.
// We use that internally because
// that's the 'peer' in the 'System.peers' table and avoids querying the full peers table in
// ControlConnection.refreshNodeInfo.
private volatile InetSocketAddress broadcastSocketAddress;
// The listen_address as known by Cassandra.
// This is usually the same as broadcast_address unless
// specified otherwise in cassandra.yaml file.
private volatile InetSocketAddress listenSocketAddress;
private volatile UUID hostId;
private volatile UUID schemaVersion;
private volatile String serverId;
private volatile int nativeTransportPort;
private volatile int nativeTransportPortSsl;
private volatile int storagePort;
private volatile int storagePortSsl;
private volatile int jmxPort;
enum State {
ADDED,
DOWN,
UP
}
volatile State state;
/** Ensures state change notifications for that host are handled serially */
final ReentrantLock notificationsLock = new ReentrantLock(true);
final ConvictionPolicy convictionPolicy;
private final Cluster.Manager manager;
// Tracks later reconnection attempts to that host so we avoid adding multiple tasks.
final AtomicReference> reconnectionAttempt =
new AtomicReference>();
final ExecutionInfo defaultExecutionInfo;
private volatile String datacenter;
private volatile String rack;
private volatile VersionNumber cassandraVersion;
private volatile Set tokens;
private volatile Set dseWorkloads = Collections.emptySet();
private volatile Boolean dseGraphEnabled;
private volatile VersionNumber dseVersion;
Host(
EndPoint endPoint,
ConvictionPolicy.Factory convictionPolicyFactory,
Cluster.Manager manager) {
if (endPoint == null || convictionPolicyFactory == null) throw new NullPointerException();
this.endPoint = endPoint;
this.convictionPolicy = convictionPolicyFactory.create(this, manager.reconnectionPolicy());
this.manager = manager;
this.defaultExecutionInfo = new ExecutionInfo(this);
this.state = State.ADDED;
}
void setLocationInfo(String datacenter, String rack) {
this.datacenter = datacenter;
this.rack = rack;
}
void setVersion(String cassandraVersion) {
VersionNumber versionNumber = null;
try {
if (cassandraVersion != null) {
versionNumber = VersionNumber.parse(cassandraVersion);
}
} catch (IllegalArgumentException e) {
logger.warn(
"Error parsing Cassandra version {}. This shouldn't have happened", cassandraVersion);
}
this.cassandraVersion = versionNumber;
}
void setBroadcastRpcAddress(InetSocketAddress broadcastRpcAddress) {
this.broadcastRpcAddress = broadcastRpcAddress;
}
void setBroadcastSocketAddress(InetSocketAddress broadcastAddress) {
this.broadcastSocketAddress = broadcastAddress;
}
void setListenSocketAddress(InetSocketAddress listenAddress) {
this.listenSocketAddress = listenAddress;
}
void setDseVersion(String dseVersion) {
VersionNumber versionNumber = null;
try {
if (dseVersion != null) {
versionNumber = VersionNumber.parse(dseVersion);
}
} catch (IllegalArgumentException e) {
logger.warn("Error parsing DSE version {}. This shouldn't have happened", dseVersion);
}
this.dseVersion = versionNumber;
}
void setDseWorkloads(Set dseWorkloads) {
this.dseWorkloads = Collections.unmodifiableSet(dseWorkloads);
}
void setDseGraphEnabled(boolean dseGraphEnabled) {
this.dseGraphEnabled = dseGraphEnabled;
}
void setHostId(UUID hostId) {
this.hostId = hostId;
}
void setSchemaVersion(UUID schemaVersion) {
this.schemaVersion = schemaVersion;
}
void setServerId(String serverId) {
this.serverId = serverId;
}
void setNativeTransportPort(int nativeTransportPort) {
this.nativeTransportPort = nativeTransportPort;
}
void setNativeTransportPortSsl(int nativeTransportPortSsl) {
this.nativeTransportPortSsl = nativeTransportPortSsl;
}
void setStoragePort(int storagePort) {
this.storagePort = storagePort;
}
void setStoragePortSsl(int storagePortSsl) {
this.storagePortSsl = storagePortSsl;
}
void setJmxPort(int jmxPort) {
this.jmxPort = jmxPort;
}
boolean supports(ProtocolVersion version) {
return getCassandraVersion() == null
|| version.minCassandraVersion().compareTo(getCassandraVersion().nextStable()) <= 0;
}
/** Returns information to connect to the node. */
public EndPoint getEndPoint() {
return endPoint;
}
/**
* Returns the address that the driver will use to connect to the node.
*
* @deprecated This is exposed mainly for historical reasons. Internally, the driver uses {@link
* #getEndPoint()} to establish connections. This is a shortcut for {@code
* getEndPoint().resolve().getAddress()}.
*/
@Deprecated
public InetAddress getAddress() {
return endPoint.resolve().getAddress();
}
/**
* Returns the address and port that the driver will use to connect to the node.
*
* @deprecated This is exposed mainly for historical reasons. Internally, the driver uses {@link
* #getEndPoint()} to establish connections. This is a shortcut for {@code
* getEndPoint().resolve()}.
* @see The
* cassandra.yaml configuration file
*/
@Deprecated
public InetSocketAddress getSocketAddress() {
return endPoint.resolve();
}
/**
* Returns the broadcast RPC address, as reported by the node.
*
* This is address reported in {@code system.peers.rpc_address} (Cassandra 3) or {@code
* system.peers_v2.native_address/native_port} (Cassandra 4+).
*
*
Note that this is not necessarily the address that the driver will use to connect: if the
* node is accessed through a proxy, a translation might be necessary; this is handled by {@link
* #getEndPoint()}.
*
*
For versions of Cassandra less than 2.0.16, 2.1.6 or 2.2.0-rc1, this will be {@code null}
* for the control host. It will get updated if the control connection switches to another host.
*
* @see CASSANDRA-9436 (where the
* information was added for the control host)
*/
public InetSocketAddress getBroadcastRpcAddress() {
return broadcastRpcAddress;
}
/**
* Returns the node broadcast address, if known. Otherwise {@code null}.
*
*
This is a shortcut for {@code getBroadcastSocketAddress().getAddress()}.
*
* @return the node broadcast address, if known. Otherwise {@code null}.
* @see #getBroadcastSocketAddress()
* @see The
* cassandra.yaml configuration file
*/
public InetAddress getBroadcastAddress() {
return broadcastSocketAddress != null ? broadcastSocketAddress.getAddress() : null;
}
/**
* Returns the node broadcast address (that is, the address by which it should be contacted by
* other peers in the cluster), if known. Otherwise {@code null}.
*
*
Note that the port of the returned address will be 0 for versions of Cassandra older than
* 4.0.
*
*
This corresponds to the {@code broadcast_address} cassandra.yaml file setting and is by
* default the same as {@link #getListenSocketAddress()}, unless specified otherwise in
* cassandra.yaml. This is NOT the address clients should use to contact this node.
*
*
This information is always available for peer hosts. For the control host, it's only
* available if CASSANDRA-9436 is fixed on the server side (Cassandra versions >= 2.0.16, 2.1.6,
* 2.2.0 rc1). For older versions, note that if the driver loses the control connection and
* reconnects to a different control host, the old control host becomes a peer, and therefore its
* broadcast address is updated.
*
* @return the node broadcast address, if known. Otherwise {@code null}.
* @see The
* cassandra.yaml configuration file
*/
public InetSocketAddress getBroadcastSocketAddress() {
return broadcastSocketAddress;
}
/**
* Returns the node listen address, if known. Otherwise {@code null}.
*
*
This is a shortcut for {@code getListenSocketAddress().getAddress()}.
*
* @return the node listen address, if known. Otherwise {@code null}.
* @see #getListenSocketAddress()
* @see The
* cassandra.yaml configuration file
*/
public InetAddress getListenAddress() {
return listenSocketAddress != null ? listenSocketAddress.getAddress() : null;
}
/**
* Returns the node listen address (that is, the address the node uses to contact other peers in
* the cluster), if known. Otherwise {@code null}.
*
*
Note that the port of the returned address will be 0 for versions of Cassandra older than
* 4.0.
*
*
This corresponds to the {@code listen_address} cassandra.yaml file setting. This is NOT
* the address clients should use to contact this node.
*
*
This information is available for the control host if CASSANDRA-9603 is fixed on the server
* side (Cassandra versions >= 2.0.17, 2.1.8, 2.2.0 rc2). It's currently not available for peer
* hosts. Note that the current driver code already tries to read a {@code listen_address} column
* in {@code system.peers}; when a future Cassandra version adds it, it will be picked by the
* driver without any further change needed.
*
* @return the node listen address, if known. Otherwise {@code null}.
* @see The
* cassandra.yaml configuration file
*/
public InetSocketAddress getListenSocketAddress() {
return listenSocketAddress;
}
/**
* Returns the name of the datacenter this host is part of.
*
*
The returned datacenter name is the one as known by Cassandra. It is also possible for this
* information to be unavailable. In that case this method returns {@code null}, and the caller
* should always be aware of this possibility.
*
* @return the Cassandra datacenter name or null if datacenter is unavailable.
*/
public String getDatacenter() {
return datacenter;
}
/**
* Returns the name of the rack this host is part of.
*
*
The returned rack name is the one as known by Cassandra. It is also possible for this
* information to be unavailable. In that case this method returns {@code null}, and the caller
* should always be aware of this possibility.
*
* @return the Cassandra rack name or null if the rack is unavailable
*/
public String getRack() {
return rack;
}
/**
* The Cassandra version the host is running.
*
*
It is also possible for this information to be unavailable. In that case this method returns
* {@code null}, and the caller should always be aware of this possibility.
*
* @return the Cassandra version the host is running.
*/
public VersionNumber getCassandraVersion() {
return cassandraVersion;
}
/**
* The DSE version the host is running.
*
*
It is also possible for this information to be unavailable. In that case this method returns
* {@code null}, and the caller should always be aware of this possibility.
*
* @return the DSE version the host is running.
*/
public VersionNumber getDseVersion() {
return dseVersion;
}
/**
* The main DSE Workload the host is running.
*
*
It is also possible for this information to be unavailable. In that case this method returns
* {@code null}, and the caller should always be aware of this possibility.
*
* @return the DSE workload the host is running.
* @deprecated This method returns only the first workload reported by the host; use {@link
* #getDseWorkloads()} instead.
*/
@Deprecated
public String getDseWorkload() {
return dseWorkloads.isEmpty() ? null : dseWorkloads.iterator().next();
}
/**
* The DSE Workloads the host is running.
*
*
This is based on the "workload" or "workloads" columns in {@code system.local} and {@code
* system.peers}.
*
*
Workload labels may vary depending on the DSE version in use; e.g. DSE 5.1 may report two
* distinct workloads: {@code Search} and {@code Analytics}, while DSE 5.0 would report a single
* {@code SearchAnalytics} workload instead. It is up to users to deal with such discrepancies;
* the driver simply returns the workload labels as reported by DSE, without any form of
* pre-processing.
*
*
The returned set is immutable. It is also possible for this information to be unavailable.
* In that case this method returns an empty set, and the caller should always be aware of this
* possibility.
*
* @return the DSE workloads the host is running.
*/
public Set getDseWorkloads() {
return dseWorkloads;
}
/**
* Returns whether the host is running DSE Graph.
*
* This is based on the "graph" column in {@code system.local} and {@code system.peers}.
*
* @return whether the node is running DSE Graph.
* @deprecated As of DSE 5.1, users should determine whether this host has the graph workload
* enabled by inspecting the contents of {@link #getDseWorkloads()} instead. Note that this
* method will throw {@link UnsupportedOperationException} if the system tables do not contain
* a "graph" column.
*/
@Deprecated
public boolean isDseGraphEnabled() {
if (dseGraphEnabled == null) {
throw new UnsupportedOperationException(
"No 'graph' column in system tables. Try getDseWorkloads() instead.");
} else {
return dseGraphEnabled;
}
}
/**
* Return the host id value for the host.
*
*
The host id is the main identifier used by Cassandra on the server for internal
* communication (gossip). It is referenced as the column {@code host_id} in the {@code
* system.local} or {@code system.peers} table.
*
* @return the node's host id value.
*/
public UUID getHostId() {
return hostId;
}
/**
* Return the current schema version for the host.
*
*
Schema versions in Cassandra are used to ensure all the nodes agree on the current Cassandra
* schema when it is modified. For more information see {@link
* ExecutionInfo#isSchemaInAgreement()}
*
* @return the node's current schema version value.
*/
public UUID getSchemaVersion() {
return schemaVersion;
}
/**
* Returns the tokens that this host owns.
*
* @return the (immutable) set of tokens.
*/
public Set getTokens() {
return tokens;
}
void setTokens(Set tokens) {
this.tokens = tokens;
}
/**
* Returns whether the host is considered up by the driver.
*
* Please note that this is only the view of the driver and may not reflect reality. In
* particular a node can be down but the driver hasn't detected it yet, or it can have been
* restarted and the driver hasn't detected it yet (in particular, for hosts to which the driver
* does not connect (because the {@code LoadBalancingPolicy.distance} method says so), this
* information may be durably inaccurate). This information should thus only be considered as best
* effort and should not be relied upon too strongly.
*
* @return whether the node is considered up.
*/
public boolean isUp() {
return state == State.UP;
}
/**
* Returns a description of the host's state, as seen by the driver.
*
*
This is exposed for debugging purposes only; the format of this string might change between
* driver versions, so clients should not make any assumptions about it.
*
* @return a description of the host's state.
*/
public String getState() {
return state.name();
}
/**
* Returns a {@code ListenableFuture} representing the completion of the reconnection attempts
* scheduled after a host is marked {@code DOWN}.
*
*
If the caller cancels this future, the driver will not try to reconnect to this host
* until it receives an UP event for it. Note that this could mean never, if the node was marked
* down because of a driver-side error (e.g. read timeout) but no failure was detected by
* Cassandra. The caller might decide to trigger an explicit reconnection attempt at a later point
* with {@link #tryReconnectOnce()}.
*
* @return the future, or {@code null} if no reconnection attempt was in progress.
*/
public ListenableFuture> getReconnectionAttemptFuture() {
return reconnectionAttempt.get();
}
/**
* Triggers an asynchronous reconnection attempt to this host.
*
*
This method is intended for load balancing policies that mark hosts as {@link
* HostDistance#IGNORED IGNORED}, but still need a way to periodically check these hosts' states
* (UP / DOWN).
*
*
For a host that is at distance {@code IGNORED}, this method will try to reconnect exactly
* once: if reconnection succeeds, the host is marked {@code UP}; otherwise, no further attempts
* will be scheduled. It has no effect if the node is already {@code UP}, or if a reconnection
* attempt is already in progress.
*
*
Note that if the host is not a distance {@code IGNORED}, this method will
* trigger a periodic reconnection attempt if the reconnection fails.
*/
public void tryReconnectOnce() {
this.manager.startSingleReconnectionAttempt(this);
}
/**
* Returns the value of the {@code server_id} field in the {@code peers} system table for this
* node.
*
*
The {@code server_id} is the single identifier of the machine running a DSE instance. If DSE
* has been configured with DSE
* Multi-Instance, the {@code server_id} helps identifying the single physical machine that
* runs the multiple DSE instances. If DSE is not configured with DSE Multi-Instance, the {@code
* server_id} will be automatically set and be unique for each host.
*
*
For more information on the {@code server_id} see the
* documentation.
*
* @return the node's server id value. This information is only available if connecting the driver
* to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public String getDseServerId() {
return serverId;
}
/**
* Returns the port for the native transport connections on the DSE node.
*
*
The native transport port is {@code 9042} by default but can be changed on instances
* requiring specific firewall configurations. This can be configured in the {@code
* cassandra.yaml} configuration file under the {@code native_transport_port} property.
*
*
If a specific port is configured on a node, it will be exposed via this method.
*
* @return the node's native transport port value. This information is only available if
* connecting the driver to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public int getNativeTransportPort() {
return nativeTransportPort;
}
/**
* Returns the port for the encrypted native transport connections on the DSE node.
*
*
In most scenarios enabling client communications in DSE will result in using a single port
* that will only accept encrypted connections (by default the port {@code 9042} is reused since
* unencrypted connections are not allowed).
*
*
However, it is possible to configure DSE to use both encrypted and a non-encrypted
* communication ports with clients. In that case the port accepting encrypted connections will
* differ from the non-encrypted one (see {@link #getNativeTransportPort}) and will be exposed via
* this method.
*
* @return the node's encrypted native transport port value. This information is only available if
* connecting the driver to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public int getNativeTransportPortSsl() {
return nativeTransportPortSsl;
}
/**
* Returns the storage port used by the DSE node.
*
*
The storage port is used for internal communication between the DSE server nodes. This port
* is never used by the driver.
*
* @return the node's storage port value. This information is only available if connecting the
* driver to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public int getStoragePort() {
return storagePort;
}
/**
* Returns the encrypted storage port used by the DSE node.
*
*
If inter-node encryption is enabled on the DSE cluster, nodes will communicate securely
* between each other via this port. This port is never used by the driver.
*
* @return the node's encrypted storage port value. This information is only available if
* connecting the driver to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public int getStoragePortSsl() {
return storagePortSsl;
}
/**
* Returns the JMX port used by this node.
*
*
The JMX port can be configured in the {@code cassandra-env.sh} configuration file separately
* on each node.
*
* @return the node's JMX port value. This information is only available if connecting the driver
* to a DSE 6.0+ node. Otherwise this returns {@code null}.
*/
public int getJmxPort() {
return jmxPort;
}
@Override
public boolean equals(Object other) {
if (other instanceof Host) {
Host that = (Host) other;
return this.endPoint.equals(that.endPoint);
}
return false;
}
@Override
public int hashCode() {
return endPoint.hashCode();
}
boolean wasJustAdded() {
return state == State.ADDED;
}
@Override
public String toString() {
return endPoint.toString();
}
void setDown() {
state = State.DOWN;
}
void setUp() {
state = State.UP;
}
/**
* Interface for listeners that are interested in hosts added, up, down and removed events.
*
*
It is possible for the same event to be fired multiple times, particularly for up or down
* events. Therefore, a listener should ignore the same event if it has already been notified of a
* node's state.
*/
public interface StateListener {
/**
* Called when a new node is added to the cluster.
*
*
The newly added node should be considered up.
*
* @param host the host that has been newly added.
*/
void onAdd(Host host);
/**
* Called when a node is determined to be up.
*
* @param host the host that has been detected up.
*/
void onUp(Host host);
/**
* Called when a node is determined to be down.
*
* @param host the host that has been detected down.
*/
void onDown(Host host);
/**
* Called when a node is removed from the cluster.
*
* @param host the removed host.
*/
void onRemove(Host host);
/**
* Gets invoked when the tracker is registered with a cluster, or at cluster startup if the
* tracker was registered at initialization with {@link
* com.datastax.driver.core.Cluster.Initializer#register(LatencyTracker)}.
*
* @param cluster the cluster that this tracker is registered with.
*/
void onRegister(Cluster cluster);
/**
* Gets invoked when the tracker is unregistered from a cluster, or at cluster shutdown if the
* tracker was not unregistered.
*
* @param cluster the cluster that this tracker was registered with.
*/
void onUnregister(Cluster cluster);
}
/**
* A {@code StateListener} that tracks when it gets registered or unregistered with a cluster.
*
*
This interface exists only for backward-compatibility reasons: starting with the 3.0 branch
* of the driver, its methods are on the parent interface directly.
*/
public interface LifecycleAwareStateListener extends StateListener {
/**
* Gets invoked when the listener is registered with a cluster, or at cluster startup if the
* listener was registered at initialization with {@link
* com.datastax.driver.core.Cluster#register(Host.StateListener)}.
*
* @param cluster the cluster that this listener is registered with.
*/
@Override
void onRegister(Cluster cluster);
/**
* Gets invoked when the listener is unregistered from a cluster, or at cluster shutdown if the
* listener was not unregistered.
*
* @param cluster the cluster that this listener was registered with.
*/
@Override
void onUnregister(Cluster cluster);
}
}