brooklyn.entity.nosql.cassandra.CassandraNodeImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of brooklyn-software-nosql Show documentation
Show all versions of brooklyn-software-nosql Show documentation
Brooklyn entities for NoSQL data store software entities
/*
* Copyright 2012-2013 by Cloudsoft Corp.
*/
package brooklyn.entity.nosql.cassandra;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.management.ObjectName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.enricher.RollingTimeWindowMeanEnricher;
import brooklyn.enricher.TimeWeightedDeltaEnricher;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.SoftwareProcessImpl;
import brooklyn.entity.java.JavaAppUtils;
import brooklyn.event.AttributeSensor;
import brooklyn.event.feed.function.FunctionFeed;
import brooklyn.event.feed.function.FunctionPollConfig;
import brooklyn.event.feed.jmx.JmxAttributePollConfig;
import brooklyn.event.feed.jmx.JmxFeed;
import brooklyn.event.feed.jmx.JmxHelper;
import brooklyn.event.feed.jmx.JmxOperationPollConfig;
import brooklyn.location.MachineLocation;
import brooklyn.location.MachineProvisioningLocation;
import brooklyn.location.basic.Machines;
import brooklyn.location.cloud.CloudLocationConfig;
import brooklyn.util.collections.MutableSet;
import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
/**
* Implementation of {@link CassandraNode}.
*/
public class CassandraNodeImpl extends SoftwareProcessImpl implements CassandraNode {
private static final Logger log = LoggerFactory.getLogger(CassandraNodeImpl.class);
private final AtomicReference requiresAlwaysPublicIp = new AtomicReference();
public CassandraNodeImpl() {
}
/**
* Some clouds (e.g. Rackspace) give us VMs that have two nics: one for private and one for public.
* If the private IP is used then it doesn't work, even for a cluster purely internal to Rackspace!
*
* TODO Need to investigate that further, e.g.:
* - is `openIptables` opening it up for both interfaces?
* - for aws->rackspace comms between nodes (thus using the public IP), will it be listening on an accessible port?
*
* FIXME Really ugly code; surely can do better?!
*
* @return
*/
protected boolean requiresAlwaysPublicIp() {
if (requiresAlwaysPublicIp.get() != null) {
return requiresAlwaysPublicIp.get();
}
MachineProvisioningLocation> loc = getProvisioningLocation();
if (loc != null) {
try {
Method method = loc.getClass().getMethod("getProvider");
method.setAccessible(true);
String provider = (String) method.invoke(loc);
boolean result = (provider != null) && (provider.contains("rackspace") || provider.contains("cloudservers"));
log.info("Inferred requiresAlwaysPublicIp={} for {}; using location {}, based on provider {}", new Object[] {result, this, loc, provider});
requiresAlwaysPublicIp.set(result);
} catch (Exception e) {
log.info("Inferred requiresAlwaysPublicIp={} for {}; using location {}, based on: {}", new Object[] {false, this, loc, e});
requiresAlwaysPublicIp.set(false);
}
return requiresAlwaysPublicIp.get();
}
return false;
}
@Override public Integer getGossipPort() { return getAttribute(CassandraNode.GOSSIP_PORT); }
@Override public Integer getSslGossipPort() { return getAttribute(CassandraNode.SSL_GOSSIP_PORT); }
@Override public Integer getThriftPort() { return getAttribute(CassandraNode.THRIFT_PORT); }
@Override public String getClusterName() { return getAttribute(CassandraNode.CLUSTER_NAME); }
@Override public Long getToken() { return getAttribute(CassandraNode.TOKEN); }
@Override public String getListenAddress() {
if (requiresAlwaysPublicIp()) {
return getAttribute(CassandraNode.ADDRESS);
} else {
String subnetAddress = getAttribute(CassandraNode.SUBNET_ADDRESS);
return Strings.isNonBlank(subnetAddress) ? subnetAddress : getAttribute(CassandraNode.ADDRESS);
}
}
@Override public String getBroadcastAddress() {
String snitchName = getConfig(CassandraNode.ENDPOINT_SNITCH_NAME);
if (snitchName.equals("Ec2MultiRegionSnitch") || snitchName.contains("MultiCloudSnitch")) {
// http://www.datastax.com/documentation/cassandra/2.0/mobile/cassandra/architecture/architectureSnitchEC2MultiRegion_c.html
// describes that the listen_address is set to the private IP, and the broadcast_address is set to the public IP.
return getPublicIp();
} else if (!getDriver().isClustered()) {
return getListenAddress();
} else {
// In other situations, prefer the hostname so other regions can see it
return getAttribute(CassandraNode.HOSTNAME);
}
}
public String getPrivateIp() {
if (requiresAlwaysPublicIp()) {
return getAttribute(CassandraNode.ADDRESS);
} else {
String subnetAddress = getAttribute(CassandraNode.SUBNET_ADDRESS);
return Strings.isNonBlank(subnetAddress) ? subnetAddress : getAttribute(CassandraNode.ADDRESS);
}
}
public String getPublicIp() {
return getAttribute(CassandraNode.ADDRESS);
}
@Override public String getSeeds() {
Set seeds = getConfig(CassandraNode.INITIAL_SEEDS);
if (seeds==null) {
log.warn("No seeds available when requested for "+this, new Throwable("source of no Cassandra seeds when requested"));
return null;
}
String snitchName = getConfig(CassandraNode.ENDPOINT_SNITCH_NAME);
MutableSet seedsHostnames = MutableSet.of();
for (Entity entity : seeds) {
// tried removing ourselves if there are other nodes, but that is a BAD idea!
// blows up with a "java.lang.RuntimeException: No other nodes seen!"
if (snitchName.equals("Ec2MultiRegionSnitch") || snitchName.contains("MultiCloudSnitch")) {
// http://www.datastax.com/documentation/cassandra/2.0/mobile/cassandra/architecture/architectureSnitchEC2MultiRegion_c.html
// says the seeds should be public IPs.
seedsHostnames.add(entity.getAttribute(CassandraNode.ADDRESS));
} else if (requiresAlwaysPublicIp()) {
seedsHostnames.add(entity.getAttribute(CassandraNode.HOSTNAME));
} else {
String seedHostname = Machines.findSubnetOrPublicHostname(entity).get();
seedsHostnames.add(seedHostname);
}
}
String result = Strings.join(seedsHostnames, ",");
log.info("Seeds for {}: {}", this, result);
return result;
}
public String getDatacenterName() {
String name = getAttribute(CassandraNode.DATACENTER_NAME);
if (name == null) {
MachineLocation machine = getMachineOrNull();
MachineProvisioningLocation> provisioningLocation = getProvisioningLocation();
if (machine != null) {
name = machine.getConfig(CloudLocationConfig.CLOUD_REGION_ID);
}
if (name == null && provisioningLocation != null) {
name = provisioningLocation.getConfig(CloudLocationConfig.CLOUD_REGION_ID);
}
if (name == null) {
name = "UNKNOWN_DATACENTER";
}
setAttribute((AttributeSensor)DATACENTER_NAME, name);
}
return name;
}
public String getRackName() {
String name = getAttribute(CassandraNode.RACK_NAME);
if (name == null) {
MachineLocation machine = getMachineOrNull();
MachineProvisioningLocation> provisioningLocation = getProvisioningLocation();
if (machine != null) {
name = machine.getConfig(CloudLocationConfig.CLOUD_AVAILABILITY_ZONE_ID);
}
if (name == null && provisioningLocation != null) {
name = provisioningLocation.getConfig(CloudLocationConfig.CLOUD_AVAILABILITY_ZONE_ID);
}
if (name == null) {
name = "UNKNOWN_RACK";
}
setAttribute((AttributeSensor)RACK_NAME, name);
}
return name;
}
@Override
public Class getDriverInterface() {
return CassandraNodeDriver.class;
}
public CassandraNodeDriver getDriver() {
return (CassandraNodeDriver) super.getDriver();
}
@Override
public void init() {
super.init();
}
private volatile JmxFeed jmxFeed;
private volatile FunctionFeed functionFeed;
private JmxHelper jmxHelper;
private ObjectName storageServiceMBean = JmxHelper.createObjectName("org.apache.cassandra.db:type=StorageService");
private ObjectName readStageMBean = JmxHelper.createObjectName("org.apache.cassandra.request:type=ReadStage");
private ObjectName mutationStageMBean = JmxHelper.createObjectName("org.apache.cassandra.request:type=MutationStage");
private ObjectName snitchMBean = JmxHelper.createObjectName("org.apache.cassandra.db:type=EndpointSnitchInfo");
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void connectSensors() {
super.connectSensors();
jmxHelper = new JmxHelper(this);
jmxFeed = JmxFeed.builder()
.entity(this)
.period(3000, TimeUnit.MILLISECONDS)
.helper(jmxHelper)
.pollAttribute(new JmxAttributePollConfig(SERVICE_UP_JMX)
.objectName(storageServiceMBean)
.attributeName("Initialized")
.onSuccess(Functions.forPredicate(Predicates.notNull()))
.onException(Functions.constant(false)))
.pollAttribute(new JmxAttributePollConfig(TOKEN)
.objectName(storageServiceMBean)
.attributeName("TokenToEndpointMap")
.onSuccess((Function) new Function