blob: 0acd1624cbc6727ffe3f918adbea1255c116c5eb [file] [log] [blame]
/*
* Copyright (C) 2012-2015 DataStax 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.datastax.driver.core;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
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);
private final InetSocketAddress address;
enum State { ADDED, DOWN, UP }
volatile State state;
/** Ensures state change notifications for that host are handled serially */
final ReentrantLock notificationsLock = new ReentrantLock(true);
private final ConvictionPolicy policy;
private final Cluster.Manager manager;
// Tracks later reconnection attempts to that host so we avoid adding multiple tasks.
final AtomicReference<ListenableFuture<?>> reconnectionAttempt = new AtomicReference<ListenableFuture<?>>();
final ExecutionInfo defaultExecutionInfo;
private volatile String datacenter;
private volatile String rack;
private volatile VersionNumber cassandraVersion;
// The listen_address (really, the broadcast one) as know 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. We don't want to expose however because we don't always have the info
// (partly because the 'System.local' doesn't have it for some weird reason for instance).
volatile InetAddress listenAddress;
private volatile Set<Token> tokens;
// ClusterMetadata keeps one Host object per inet address and we rely on this (more precisely,
// we rely on the fact that we can use Object equality as a valid equality), so don't use
// that constructor but ClusterMetadata.getHost instead.
Host(InetSocketAddress address, ConvictionPolicy.Factory policy, Cluster.Manager manager) {
if (address == null || policy == null)
throw new NullPointerException();
this.address = address;
this.policy = policy.create(this);
this.manager = manager;
this.defaultExecutionInfo = new ExecutionInfo(ImmutableList.of(this));
this.state = State.ADDED;
}
void setLocationInfo(String datacenter, String rack) {
this.datacenter = datacenter;
this.rack = rack;
}
void setVersionAndListenAdress(String cassandraVersion, InetAddress listenAddress) {
if (listenAddress != null)
this.listenAddress = listenAddress;
if (cassandraVersion == null)
return;
try {
this.cassandraVersion = VersionNumber.parse(cassandraVersion);
} catch (IllegalArgumentException e) {
logger.warn("Error parsing Cassandra version {}. This shouldn't have happened", cassandraVersion);
}
}
/**
* Returns the node address.
* <p>
* This is a shortcut for {@code getSocketAddress().getAddress()}.
*
* @return the node {@link InetAddress}.
*/
public InetAddress getAddress() {
return address.getAddress();
}
/**
* Returns the node socket address.
*
* @return the node {@link InetSocketAddress}.
*/
public InetSocketAddress getSocketAddress() {
return address;
}
/**
* Returns the name of the datacenter this host is part of.
* <p>
* 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.
* <p>
* 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 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.
* <p>
* As for other host information fetch from Cassandra above, the returned
* version can theoretically be null if the information is unavailable.
*
* @return the Cassandra version the host is running.
*/
public VersionNumber getCassandraVersion() {
return cassandraVersion;
}
/**
* Returns the tokens that this host owns.
*
* @return the (immutable) set of tokens.
*/
public Set<Token> getTokens() {
return tokens;
}
void setTokens(Set<Token> tokens) {
this.tokens = tokens;
}
/**
* Returns whether the host is considered up by the driver.
* <p>
* 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.
* <p>
* 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}.
* <p>
* <b>If the caller cancels this future,</b> 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.
* <p>
* 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).
* <p>
* 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.
* <p>
* Note that if the host is <em>not</em> a distance {@code IGNORED}, this method <em>will</em> trigger a periodic
* reconnection attempt if the reconnection fails.
*/
public void tryReconnectOnce() {
this.manager.startSingleReconnectionAttempt(this);
}
@Override
public boolean equals(Object other) {
if (other instanceof Host) {
Host that = (Host)other;
return this.address.equals(that.address);
}
return false;
}
@Override
public int hashCode() {
return address.hashCode();
}
boolean wasJustAdded() {
return state == State.ADDED;
}
@Override
public String toString() {
return address.toString();
}
void setDown() {
state = State.DOWN;
}
void setUp() {
policy.reset();
state = State.UP;
}
boolean signalConnectionFailure(ConnectionException exception) {
return policy.addFailure(exception);
}
/**
* Interface for listeners that are interested in hosts added, up, down and
* removed events.
* <p>
* 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.
* <p>
* 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);
}
}