/*
* @(#) GossipNetworkTopologySnith.java
* Created Apr 10, 2012 by oleg
* (C) ONE, SIA
*/
package org.apache.cassandra.locator;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndPointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.gms.IEndPointStateChangeSubscriber;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.CopyOnWriteMap;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This snitch takes local node location information from cassandra.location system property of the form
* DatacenterName:RackName
*
* It learns about other endpoints location information using application states distributed by
* other nodes in gossip.
*
* @author Oleg Anastasyev<oa@hq.one.lv>
*
*/
public class GossipNetworkTopologySnith extends AbstractNetworkTopologySnitch implements IEndPointStateChangeSubscriber
{
private static final Logger logger = LoggerFactory.getLogger(GossipNetworkTopologySnith.class);
private static final Map<InetAddress, String[]> endpointMap = new CopyOnWriteMap<InetAddress, String[]>();
protected static volatile String[] defaultDCRack = null;
/**
*
*/
public GossipNetworkTopologySnith()
{
if (DatabaseDescriptor.getAllowedLocations()==null)
{
throw new RuntimeException("Allowed locations must be configured for GossipNetworkTopologySnitch");
}
putEndpoints(Collections.singletonMap(FBUtilities.getLocalAddress(), DatabaseDescriptor.getLocation().split(":")));
}
/**
* Return the data center for which an endpoint resides in
*
* @param endpoint the endpoint to process
* @return string of data center
*/
public String getDatacenter(InetAddress endpoint)
{
return getEndpointInfo(endpoint)[0];
}
/**
* Return the rack for which an endpoint resides in
*
* @param endpoint the endpoint to process
* @return string of rack
*/
public String getRack(InetAddress endpoint)
{
return getEndpointInfo(endpoint)[1];
}
/**
* Get the raw information about an end point
*
* @param endpoint endpoint to process
* @return a array of string with the first index being the data center and the second being the rack
*/
public String[] getEndpointInfo(InetAddress endpoint)
{
String[] value = endpointMap.get(endpoint);
if (value == null)
{
logger.debug("Could not find end point information for {}, will use default", endpoint);
if ( defaultDCRack == null )
throw new RuntimeException("Could not find topology info for "+endpoint+": It is not in cassandra-topology.properties and there is no default");
return defaultDCRack;
}
return value;
}
/**
* @param reloadedMap
* @return
*/
private String printTopology(Map<InetAddress, String[]> reloadedMap)
{
StringBuilder sb = new StringBuilder();
for (Entry<InetAddress, String[]> a : reloadedMap.entrySet())
{
sb.append(a.getKey()+" => "+ Arrays.toString(a.getValue()));
}
return sb.toString();
}
NavigableSet<String> getConfiguredRacks()
{
TreeSet<String> racks = new TreeSet<String>();
for (String info : DatabaseDescriptor.getAllowedLocations()) {
racks.add(info.split(":")[1]);
}
if (defaultDCRack!=null)
racks.add(defaultDCRack[1]);
return racks;
}
/* (non-Javadoc)
* @see org.apache.cassandra.locator.AbstractEndpointSnitch#getLocalDatacenter()
*/
@Override
public String getLocalDatacenter()
{
String dc = DatabaseDescriptor.getLocation();
return dc == null ? getEndpointInfo(FBUtilities.getLocalAddress())[0] : dc.split(":")[0];
}
/* (non-Javadoc)
* @see org.apache.cassandra.locator.AbstractEndpointSnitch#getLocalRack()
*/
@Override
public String getLocalRack()
{
String dc = DatabaseDescriptor.getLocation();
return dc == null ? getEndpointInfo(FBUtilities.getLocalAddress())[0] : dc.split(":")[1];
}
/**
* @param endpointMap the endpointMap to set
*/
public void putEndpoints(Map<InetAddress, String[]> endp)
{
endpointMap.putAll(endp);
logger.info("set network topology {}", printTopology( endpointMap ));
}
/* (non-Javadoc)
* @see org.apache.cassandra.locator.AbstractEndpointSnitch#gossiperStarting()
*/
@Override
public void gossiperStarting()
{
Gossiper.instance.register(this);
super.gossiperStarting();
}
/* (non-Javadoc)
* @see org.apache.cassandra.gms.IEndPointStateChangeSubscriber#onJoin(java.net.InetAddress, org.apache.cassandra.gms.EndPointState)
*/
@Override
public void onJoin(InetAddress endpoint, EndPointState epState)
{
ApplicationState stateDC = epState.getApplicationState(APPSTATE_DC);
ApplicationState stateRack = epState.getApplicationState(APPSTATE_RACK);
String[] location = new String[2] ;
if (stateDC!=null)
location[0] = stateDC.getValue();
if (stateRack!=null)
location[1] = stateRack.getValue();
if (location[0] == null || location[1] == null) {
location[0] = getDatacenter(endpoint);
location[1] = getRack(endpoint);
}
if (location[0] == null || location[1] == null) {
logger.error("Cannot determine datacenter and rack location of "+endpoint+". On rack aware replication strategies secondary replicas will be broken for this endpoint");
blockFromJoin(endpoint, epState);
return;
}
if (DatabaseDescriptor.getAllowedLocations()!=null && !DatabaseDescriptor.getAllowedLocations().contains(location[0]+":"+location[1]) )
{
logger.error("Location of "+endpoint+"["+location[0]+":"+location[1]+"] is not in allowed locations list.");
blockFromJoin(endpoint,epState);
return;
}
putEndpoints(Collections.singletonMap(endpoint, location));
}
private void blockFromJoin(InetAddress endpoint, EndPointState epState)
{
logger.info("Blocking "+endpoint+" from joining ring");
epState.getApplicationStateMap().put(StorageService.MOVE_STATE, new ApplicationState(StorageService.STATE_HIBERNATE+StorageService.Delimiter+"true"));
}
/* (non-Javadoc)
* @see org.apache.cassandra.gms.IEndPointStateChangeSubscriber#onChange(java.net.InetAddress, java.lang.String, org.apache.cassandra.gms.ApplicationState)
*/
@Override
public void onChange(InetAddress endpoint, String stateName,
ApplicationState state)
{
if (stateName.equals(StorageService.MOVE_STATE) || stateName.equals(APPSTATE_DC) || stateName.equals(APPSTATE_RACK))
onJoin(endpoint, Gossiper.instance.getEndPointStateForEndPoint(endpoint));
}
/* (non-Javadoc)
* @see org.apache.cassandra.gms.IEndPointStateChangeSubscriber#onAlive(java.net.InetAddress, org.apache.cassandra.gms.EndPointState)
*/
@Override
public void onAlive(InetAddress endpoint, EndPointState state)
{
onJoin(endpoint, state);
}
/* (non-Javadoc)
* @see org.apache.cassandra.gms.IEndPointStateChangeSubscriber#onDead(java.net.InetAddress, org.apache.cassandra.gms.EndPointState)
*/
@Override
public void onDead(InetAddress endpoint, EndPointState state)
{
}
/* (non-Javadoc)
* @see org.apache.cassandra.gms.IEndPointStateChangeSubscriber#onRemove(java.net.InetAddress)
*/
@Override
public void onRemove(InetAddress endpoint)
{
// cannot remove info here - because node can be temporary in dead state
}
}