package me.prettyprint.cassandra.service; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import me.prettyprint.cassandra.connection.HConnectionManager; import me.prettyprint.hector.api.ClockResolution; import me.prettyprint.hector.api.Cluster; import me.prettyprint.hector.api.ddl.KeyspaceDefinition; import me.prettyprint.hector.api.exceptions.HectorException; import org.apache.cassandra.thrift.Cassandra; import org.apache.cassandra.thrift.InvalidRequestException; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A cluster instance the client side representation of a cassandra server cluster. * * The cluster is usually the main entry point for programs using hector. To start operating on * cassandra cluster you first get or create a cluster, then a keyspace operator for the keyspace * you're interested in and then create mutations of queries * <code> * //get a cluster: * Cluster cluster = getOrCreateCluster("MyCluster", new CassandraHostConfigurator("127.0.0.1:9170")); * //get a keyspace from this cluster: * Keyspace ko = createKeyspace("Keyspace1", cluster); * //Create a mutator: * Mutator m = createMutator(ko); * // Make a mutation: * MutationResult mr = m.insert("key", cf, createColumn("name", "value", serializer, serializer)); * </code> * * THREAD SAFETY: This class is thread safe. * * @author Ran Tavory * @author zznate */ public abstract class AbstractCluster implements Cluster { private static final Map<String, String> EMPTY_CREDENTIALS = Collections.emptyMap(); private final Logger log = LoggerFactory.getLogger(AbstractCluster.class); /** * Linked to Cassandra StorageProxy. */ public static final int RING_DELAY = 30 * 1000; // delay after which we assume ring has stablized public static final int WAIT_FOR_SCHEMA_AGREEMENT_SLEEP_TIME = 1000; protected final HConnectionManager connectionManager; private final String name; private final CassandraHostConfigurator configurator; private final ClockResolution clockResolution; private final FailoverPolicy failoverPolicy; private final CassandraClientMonitor cassandraClientMonitor; private Set<CassandraHost> knownPoolHosts; protected final ExceptionsTranslator xtrans; private final Map<String, String> credentials; public AbstractCluster(String clusterName, CassandraHostConfigurator cassandraHostConfigurator) { this(clusterName, cassandraHostConfigurator, EMPTY_CREDENTIALS); } public AbstractCluster(String clusterName, CassandraHostConfigurator cassandraHostConfigurator, Map<String, String> credentials) { connectionManager = new HConnectionManager(clusterName, cassandraHostConfigurator); name = clusterName; configurator = cassandraHostConfigurator; failoverPolicy = FailoverPolicy.ON_FAIL_TRY_ALL_AVAILABLE; cassandraClientMonitor = JmxMonitor.getInstance().getCassandraMonitor(connectionManager); xtrans = new ExceptionsTranslatorImpl(); clockResolution = cassandraHostConfigurator.getClockResolution(); this.credentials = Collections.unmodifiableMap(credentials == null? EMPTY_CREDENTIALS : credentials); } @Override public HConnectionManager getConnectionManager() { return connectionManager; } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#getKnownPoolHosts(boolean) */ @Override public Set<CassandraHost> getKnownPoolHosts(boolean refresh) { if (refresh || knownPoolHosts == null) { knownPoolHosts = connectionManager.getHosts(); if ( log.isInfoEnabled() ) { log.info("found knownPoolHosts: {}", knownPoolHosts); } } return knownPoolHosts; } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#addHost(me.prettyprint.cassandra.service.CassandraHost, boolean) */ @Override public void addHost(CassandraHost cassandraHost, boolean skipApplyConfig) { if (!skipApplyConfig && configurator != null) { configurator.applyConfig(cassandraHost); } connectionManager.addCassandraHost(cassandraHost); } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#getName() */ @Override public String getName() { return name; } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#describeKeyspaces() */ @Override public List<KeyspaceDefinition> describeKeyspaces() throws HectorException { Operation<List<KeyspaceDefinition>> op = new Operation<List<KeyspaceDefinition>>(OperationType.META_READ, getCredentials()) { @Override public List<KeyspaceDefinition> execute(Cassandra.Client cassandra) throws HectorException { try { return ThriftKsDef.fromThriftList(cassandra.describe_keyspaces()); } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#describeClusterName() */ @Override public String describeClusterName() throws HectorException { Operation<String> op = new Operation<String>(OperationType.META_READ, getCredentials()) { @Override public String execute(Cassandra.Client cassandra) throws HectorException { try { return cassandra.describe_cluster_name(); } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#describeThriftVersion() */ @Override public String describeThriftVersion() throws HectorException { Operation<String> op = new Operation<String>(OperationType.META_READ, getCredentials()) { @Override public String execute(Cassandra.Client cassandra) throws HectorException { try { return cassandra.describe_version(); } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#describeKeyspace(java.lang.String) */ @Override public KeyspaceDefinition describeKeyspace(final String keyspace) throws HectorException { Operation<KeyspaceDefinition> op = new Operation<KeyspaceDefinition>( OperationType.META_READ, getCredentials()) { @Override public KeyspaceDefinition execute(Cassandra.Client cassandra) throws HectorException { try { return new ThriftKsDef(cassandra.describe_keyspace(keyspace)); } catch (org.apache.cassandra.thrift.NotFoundException nfe) { setException(xtrans.translate(nfe)); return null; } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } /* (non-Javadoc) * @see me.prettyprint.cassandra.service.Cluster#getClusterName() */ @Override public String getClusterName() throws HectorException { return describeClusterName(); } @Override public String dropKeyspace(final String keyspace) throws HectorException { return dropKeyspace(keyspace, false); } @Override public String dropKeyspace(final String keyspace, final boolean waitForSchemaAgreement) throws HectorException { Operation<String> op = new Operation<String>(OperationType.META_WRITE, FailoverPolicy.FAIL_FAST, getCredentials()) { @Override public String execute(Cassandra.Client cassandra) throws HectorException { try { if (waitForSchemaAgreement) { waitForSchemaAgreement(cassandra); } String schemaId = cassandra.system_drop_keyspace(keyspace); if (waitForSchemaAgreement) { waitForSchemaAgreement(cassandra); } return schemaId; } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } @Override public String describePartitioner() throws HectorException { Operation<String> op = new Operation<String>(OperationType.META_READ, getCredentials()) { @Override public String execute(Cassandra.Client cassandra) throws HectorException { try { if ( log.isInfoEnabled() ) { log.info("in execute with client {}", cassandra); } return cassandra.describe_partitioner(); } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } @Override public String dropColumnFamily(final String keyspaceName, final String columnFamily) throws HectorException { return dropColumnFamily(keyspaceName, columnFamily, false); } @Override public String dropColumnFamily(final String keyspaceName, final String columnFamily, final boolean waitForSchemaAgreement) throws HectorException { Operation<String> op = new Operation<String>(OperationType.META_WRITE, FailoverPolicy.FAIL_FAST, keyspaceName, getCredentials()) { @Override public String execute(Cassandra.Client cassandra) throws HectorException { try { if (waitForSchemaAgreement) { waitForSchemaAgreement(cassandra); } String schemaId = cassandra.system_drop_column_family(columnFamily); if (waitForSchemaAgreement) { waitForSchemaAgreement(cassandra); } return schemaId; } catch (Exception e) { throw xtrans.translate(e); } } }; connectionManager.operateWithFailover(op); return op.getResult(); } @Override public Map<String, String> getCredentials() { return credentials; } @Override public void truncate(final String keyspaceName, final String columnFamily) throws HectorException { Operation<Void> op = new Operation<Void>(OperationType.META_WRITE, FailoverPolicy.FAIL_FAST, keyspaceName, getCredentials()) { @Override public Void execute(Cassandra.Client cassandra) throws HectorException { try { cassandra.truncate(columnFamily); } catch (Exception e) { throw xtrans.translate(e); } return null; } }; connectionManager.operateWithFailover(op); } @Override public void onStartup() { // default implementation does nothing } public CassandraHostConfigurator getConfigurator() { return configurator; } protected static void waitForSchemaAgreement(Cassandra.Client cassandra) throws InvalidRequestException, TException, InterruptedException { int waited = 0; int versions = 0; while (versions != 1) { versions = 0; Map<String, List<String>> schema = cassandra.describe_schema_versions(); for (Map.Entry<String, List<String>> entry : schema.entrySet()) { if (!entry.getKey().equals("UNREACHABLE")) versions++; } if (versions != 1) { Thread.sleep(WAIT_FOR_SCHEMA_AGREEMENT_SLEEP_TIME); waited += WAIT_FOR_SCHEMA_AGREEMENT_SLEEP_TIME; if (waited > RING_DELAY) throw new RuntimeException("Could not reach schema agreement in " + RING_DELAY + "ms"); } } } }