package com.rayo.storage.cassandra; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.cassandra.thrift.CfDef; import org.apache.cassandra.thrift.ColumnDef; import org.apache.cassandra.thrift.KsDef; import org.apache.thrift.transport.TTransportException; import org.scale7.cassandra.pelops.Bytes; import org.scale7.cassandra.pelops.Cluster; import org.scale7.cassandra.pelops.ColumnFamilyManager; import org.scale7.cassandra.pelops.KeyspaceManager; import org.scale7.cassandra.pelops.Pelops; import org.scale7.cassandra.pelops.exceptions.NotFoundException; import com.voxeo.logging.Loggerf; /** * <p>This class takes responsability of all the schema management operations.</p> * * @author martin * */ public class CassandraSchemaHandler { private final static Loggerf log = Loggerf.getLogger(CassandraSchemaHandler.class); private int schemaWaitPeriod = 200; private boolean waitForSyncing = true; /** * Tells if a Cassandra schema does exist or not * * @param cluster Cluster configuration * @param schemaName Name of the schema * * @return boolean <code>true</code> if the Cassandra schema does exist and <code>false</code> * if it does not */ public boolean schemaExists(Cluster cluster, String schemaName) throws Exception { log.debug("Searching schema %s on cluster %s", schemaName, cluster); KeyspaceManager keyspaceManager = Pelops.createKeyspaceManager(cluster); try { KsDef ksDef = keyspaceManager.getKeyspaceSchema(schemaName); if (ksDef != null) { log.debug("Found schema %s", schemaName); return true; } } catch (TTransportException te) { log.error("It looks like the Cassandra Server is down"); log.error(te.getMessage(), te); throw te; } catch (NotFoundException nfe) { log.debug("Schema %s does not exist", schemaName); } catch (org.apache.cassandra.thrift.NotFoundException nfe2) { log.debug("Schema %s does not exist", schemaName); } catch (Exception e) { // Oddly, cassandra throws a null message which causes issues with vlib logger log.error("There has been an error: " + e.getMessage()); } return false; } /** * Validates the existing schema. * * @param cluster Cluster name * @param schemaName Name of the schema * @return boolean <code>true</code> if the schema is valid and <code>false</code> * if it should be recreated * @throws Exception If there is any issue while validating the schema */ public boolean validSchema(Cluster cluster, String schemaName) throws Exception { log.debug("Validating schema %s on cluster %s", schemaName, cluster); KeyspaceManager keyspaceManager = Pelops.createKeyspaceManager(cluster); try { KsDef ksDef = keyspaceManager.getKeyspaceSchema(schemaName); if (ksDef == null) { log.debug("Keyspace not found"); return false; } if (!validateTable(ksDef, "nodes")) return false; if (!validateTable(ksDef, "applications")) return false; if (!validateTable(ksDef, "addresses")) return false; if (!validateTable(ksDef, "clients")) return false; if (!validateTable(ksDef, "ips")) return false; if (!validateTable(ksDef, "calls")) return false; if (!validateTable(ksDef, "jids")) return false; return true; } catch (TTransportException te) { log.error("It looks like the Cassandra Server is down"); log.error(te.getMessage(), te); throw te; } catch (Exception e) { log.warn(e.getMessage()); } return false; } private boolean validateTable(KsDef ksDef, String tableName) { if (getCfDef(ksDef, tableName) == null) { log.debug("Table %s not found. Schema will be recreated", tableName); return false; } return true; } /** * Creates a new Cassandra Schema. This method will drop the schema if there * is already an existing schema with that name * * @param cluster Cluster configuration * @param schemaName Name of the schema to create * @throws Exception If the schema cannot be created */ public void buildSchema(Cluster cluster, String schemaName) throws Exception { buildSchema(cluster, schemaName, true); } /** * Creates a new Cassandra Schema * * @param cluster Cluster configuration * @param schemaName Name of the schema to create * @param dropExisting <code>true</code> if the existing schema should be dropped if exists * @throws Exception If the schema cannot be created */ public void buildSchema(Cluster cluster, String schemaName, boolean dropExisting) throws Exception { log.debug("Creating a new schema: " + schemaName); KeyspaceManager keyspaceManager = Pelops.createKeyspaceManager(cluster); if (dropExisting) { dropSchema(schemaName, keyspaceManager); } KsDef ksDef = null; try { log.debug("Finding schema: " + schemaName); ksDef = keyspaceManager.getKeyspaceSchema(schemaName); } catch (Exception e) { log.debug("The schema did not exist. Creating new Cassandra schema: " + schemaName); List<CfDef> cfDefs = new ArrayList<CfDef>(); Map<String, String> ksOptions = new HashMap<String, String>(); ksOptions.put("replication_factor", "1"); ksDef = new KsDef(schemaName,"org.apache.cassandra.locator.SimpleStrategy", cfDefs); ksDef.strategy_options = ksOptions; keyspaceManager.addKeyspace(ksDef); waitToPropagate(); } createColumnFamilies(cluster, schemaName, ksDef); } /** * Drops an existing schema * * @param schemaName Name of the schema * @param keyspaceManager Keyspace manager * @throws Exception If the schema cannot be dropped */ public void dropSchema(String schemaName, KeyspaceManager keyspaceManager) throws Exception { try { log.debug("Dropping existing Cassandra schema: " + schemaName); keyspaceManager.dropKeyspace(schemaName); log.debug("Schema dropped"); waitToPropagate(); } catch (TTransportException te) { log.error("It looks like the Cassandra Server is down"); log.error(te.getMessage(), te); throw te; } catch (Exception e) { log.debug("The schema did not exist. No schema has been dropped"); } } private void createColumnFamilies(Cluster cluster, String schemaName, KsDef ksDef) throws Exception, InterruptedException { ColumnFamilyManager cfManager = Pelops.createColumnFamilyManager(cluster, schemaName); CfDef cfNode = getCfDef(ksDef, "nodes"); if (cfNode == null) { log.debug("Creating new Column Family: nodes"); cfNode = new CfDef(schemaName, "nodes") .setColumn_type(ColumnFamilyManager.CFDEF_TYPE_SUPER) .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setSubcomparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setDefault_validation_class("UTF8Type") .setGc_grace_seconds(0) .setColumn_metadata(Arrays.asList( new ColumnDef(Bytes.fromUTF8("hostname").getBytes(), ColumnFamilyManager.CFDEF_COMPARATOR_UTF8), new ColumnDef(Bytes.fromUTF8("priority").getBytes(), ColumnFamilyManager.CFDEF_COMPARATOR_INTEGER), new ColumnDef(Bytes.fromUTF8("weight").getBytes(), ColumnFamilyManager.CFDEF_COMPARATOR_INTEGER), new ColumnDef(Bytes.fromUTF8("consecutive-errors").getBytes(), ColumnFamilyManager.CFDEF_COMPARATOR_INTEGER), new ColumnDef(Bytes.fromUTF8("blacklisted").getBytes(), ColumnFamilyManager.CFDEF_COMPARATOR_UTF8) )); ksDef.addToCf_defs(cfNode); cfManager.addColumnFamily(cfNode); waitToPropagate(); } else { log.debug("Found Column Family: nodes"); } CfDef cfApplications = getCfDef(ksDef, "applications"); if (cfApplications == null) { log.debug("Creating new Column Family: applications"); cfApplications = new CfDef(schemaName, "applications") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setDefault_validation_class("UTF8Type"); ksDef.addToCf_defs(cfApplications); cfManager.addColumnFamily(cfApplications); waitToPropagate(); } else { log.debug("Found Column Family: applications"); } CfDef cfAddresses = getCfDef(ksDef, "addresses"); if (cfAddresses == null) { log.debug("Creating new Column Family: addresses"); cfAddresses = new CfDef(schemaName, "addresses") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setDefault_validation_class("UTF8Type"); ksDef.addToCf_defs(cfAddresses); cfManager.addColumnFamily(cfAddresses); waitToPropagate(); } else { log.debug("Found Column Family: addresses"); } CfDef cfClients = getCfDef(ksDef, "clients"); if (cfClients == null) { log.debug("Creating new Column Family: clients"); cfClients = new CfDef(schemaName, "clients") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setDefault_validation_class("UTF8Type") .setGc_grace_seconds(0); ksDef.addToCf_defs(cfClients); cfManager.addColumnFamily(cfClients); waitToPropagate(); } else { log.debug("Found Column Family: clients"); } CfDef cfIps = getCfDef(ksDef, "ips"); if (cfIps == null) { log.debug("Creating new Column Family: ips"); cfIps = new CfDef(schemaName, "ips"); cfIps.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(cfIps); cfManager.addColumnFamily(cfIps); waitToPropagate(); } else { log.debug("Found Column Family: ips"); } CfDef calls = getCfDef(ksDef, "calls"); if (calls == null) { log.debug("Creating new Column Family: calls"); calls = new CfDef(schemaName, "calls"); calls.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(calls); cfManager.addColumnFamily(calls); waitToPropagate(); } else { log.debug("Found Column Family: calls"); } CfDef cfJids = getCfDef(ksDef, "jids"); if (cfJids == null) { log.debug("Creating new Column Family: jids"); cfJids = new CfDef(schemaName, "jids") .setColumn_type("Super") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES) .setSubcomparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES); cfClients.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(cfJids); cfManager.addColumnFamily(cfJids); waitToPropagate(); } else { log.debug("Found Column Family: jids"); } CfDef cfMixers = getCfDef(ksDef, "mixers"); if (cfMixers == null) { log.debug("Creating new Column Family: mixers"); cfMixers = new CfDef(schemaName, "mixers") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES); cfMixers.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(cfMixers); cfManager.addColumnFamily(cfMixers); waitToPropagate(); } else { log.debug("Found Column Family: mixers"); } CfDef cfVerbs = getCfDef(ksDef, "verbs"); if (cfVerbs == null) { log.debug("Creating new Column Family: verbs"); cfVerbs = new CfDef(schemaName, "verbs") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES); cfVerbs.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(cfVerbs); cfManager.addColumnFamily(cfVerbs); waitToPropagate(); } else { log.debug("Found Column Family: verbs"); } CfDef cfFilters = getCfDef(ksDef, "filters"); if (cfFilters == null) { log.debug("Creating new Column Family: filters"); cfFilters = new CfDef(schemaName, "filters") .setComparator_type(ColumnFamilyManager.CFDEF_COMPARATOR_BYTES); cfFilters.default_validation_class = "UTF8Type"; ksDef.addToCf_defs(cfFilters); cfManager.addColumnFamily(cfFilters); waitToPropagate(); } else { log.debug("Found Column Family: filters"); } } private void waitToPropagate() throws Exception { // This is simple wait to get all the schema changes propagated to the nodes in // the cluster. Otherwise it is very easy to get a SchemaAgreementException if (waitForSyncing) { Thread.sleep(schemaWaitPeriod); } } private CfDef getCfDef(KsDef def, String table) { for(CfDef cfDef: def.getCf_defs()) { if (cfDef.name.equals(table)) { return cfDef; } } return null; } public void setSchemaWaitPeriod(int schemaWaitPeriod) { this.schemaWaitPeriod = schemaWaitPeriod; } public void setWaitForSyncing(boolean waitForSyncing) { this.waitForSyncing = waitForSyncing; } }