Author: gdusbabek
Date: Mon Jun 7 13:38:18 2010
New Revision: 952219
URL: http://svn.apache.org/viewvc?rev=952219&view=rev
Log:
detect partitioner changes and fail fast. patch by gdusbabek, reviewed by
jbellis.
Modified:
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/avro/CassandraDaemon.java
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/db/SystemTable.java
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/thrift/CassandraDaemon.java
cassandra/branches/cassandra-0.6/test/unit/org/apache/cassandra/service/MoveTest.java
Modified:
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/avro/CassandraDaemon.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/avro/CassandraDaemon.java?rev=952219&r1=952218&r2=952219&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/avro/CassandraDaemon.java
(original)
+++
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/avro/CassandraDaemon.java
Mon Jun 7 13:38:18 2010
@@ -27,6 +27,7 @@ import org.apache.avro.ipc.SocketServer;
import org.apache.avro.specific.SpecificResponder;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.CompactionManager;
+import org.apache.cassandra.db.SystemTable;
import org.apache.cassandra.db.Table;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.service.StorageService;
@@ -71,6 +72,17 @@ public class CassandraDaemon {
}
}
});
+
+ // check the system table for mismatched partitioner.
+ try
+ {
+ SystemTable.checkHealth();
+ }
+ catch (IOException e)
+ {
+ logger.error("Fatal exception during initialization", e);
+ System.exit(100);
+ }
// initialize keyspaces
for (String table : DatabaseDescriptor.getTables())
Modified:
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/db/SystemTable.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/db/SystemTable.java?rev=952219&r1=952218&r2=952219&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/db/SystemTable.java
(original)
+++
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/db/SystemTable.java
Mon Jun 7 13:38:18 2010
@@ -18,6 +18,8 @@
package org.apache.cassandra.db;
+import java.io.File;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.IOError;
@@ -37,6 +39,7 @@ import org.apache.cassandra.config.Datab
import java.net.InetAddress;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
public class SystemTable
{
@@ -48,6 +51,7 @@ public class SystemTable
private static final byte[] TOKEN = utf8("Token");
private static final byte[] GENERATION = utf8("Generation");
private static final byte[] CLUSTERNAME = utf8("ClusterName");
+ private static final byte[] PARTITIONER = utf8("Partioner");
private static StorageMetadata metadata;
private static byte[] utf8(String str)
@@ -104,6 +108,67 @@ public class SystemTable
metadata.setToken(token);
}
+
+ /**
+ * One of three things will happen if you try to read the system table:
+ * 1. files are present and you can read them: great
+ * 2. no files are there: great (new node is assumed)
+ * 3. files are present but you can't read them: bad (suspect that the
partitioner was changed).
+ * @throws IOException
+ */
+ public static void checkHealth() throws IOException
+ {
+ Table table = null;
+ try
+ {
+ table = Table.open(Table.SYSTEM_TABLE);
+ }
+ catch (AssertionError err)
+ {
+ // this happens when a user switches from OPP to RP.
+ IOException ex = new IOException("Could not read system table. Did
you change partitioners?");
+ ex.initCause(err);
+ throw ex;
+ }
+
+ SortedSet<byte[]> cols = new TreeSet<byte[]>(new BytesType());
+ cols.add(TOKEN);
+ cols.add(GENERATION);
+ cols.add(PARTITIONER);
+ QueryFilter filter = new NamesQueryFilter(LOCATION_KEY, new
QueryPath(STATUS_CF), cols);
+ ColumnFamily cf =
table.getColumnFamilyStore(STATUS_CF).getColumnFamily(filter);
+
+ if (cf == null)
+ {
+ // this is either a brand new node (there will be no files), or
the partitioner was changed from RP to OPP.
+ for (String path :
DatabaseDescriptor.getAllDataFileLocationsForTable("system"))
+ {
+ File[] dbContents = new File(path).listFiles(new
FilenameFilter()
+ {
+ public boolean accept(File dir, String name)
+ {
+ return name.endsWith(".db");
+ }
+ });
+ if (dbContents.length > 0)
+ throw new IOException("Found system table files, but they
couldn't be loaded. Did you change the partitioner?");
+ }
+ // no system files. data is either in the commit log or this is a
new node.
+ return;
+ }
+
+
+ // token and generation should *always* be there. If either are
missing, we can assume that the partitioner has
+ // been switched.
+ if (cf.getColumnCount() > 0 && (cf.getColumn(GENERATION) == null ||
cf.getColumn(TOKEN) == null))
+ throw new IOException("Couldn't read system generation or token.
Did you change the partitioner?");
+ IColumn partitionerCol = cf.getColumn(PARTITIONER);
+ if (partitionerCol != null &&
!DatabaseDescriptor.getPartitioner().getClass().getName().equals(new
String(partitionerCol.value(), "UTF-8")))
+ throw new IOException("Detected partitioner mismatch! Did you
change the partitioner?");
+ if (partitionerCol == null)
+ logger.info("Did not see a partitioner in system storage.");
+ }
+
/*
* This method reads the system table and retrieves the metadata
* associated with this storage instance. Currently we store the
@@ -125,6 +190,7 @@ public class SystemTable
columns.add(CLUSTERNAME);
QueryFilter filter = new NamesQueryFilter(LOCATION_KEY, new
QueryPath(STATUS_CF), columns);
ColumnFamily cf =
table.getColumnFamilyStore(STATUS_CF).getColumnFamily(filter);
+ String partitioner =
DatabaseDescriptor.getPartitioner().getClass().getName();
IPartitioner p = StorageService.getPartitioner();
if (cf == null)
@@ -149,8 +215,21 @@ public class SystemTable
cf.addColumn(new Column(TOKEN,
p.getTokenFactory().toByteArray(token)));
cf.addColumn(new Column(GENERATION,
FBUtilities.toByteArray(generation)));
cf.addColumn(new Column(CLUSTERNAME,
DatabaseDescriptor.getClusterName().getBytes()));
+ cf.addColumn(new Column(PARTITIONER,
partitioner.getBytes("UTF-8")));
rm.add(cf);
rm.apply();
+ try
+ {
+
table.getColumnFamilyStore(SystemTable.STATUS_CF).forceBlockingFlush();
+ }
+ catch (ExecutionException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
metadata = new StorageMetadata(token, generation,
DatabaseDescriptor.getClusterName().getBytes());
return metadata;
}
@@ -168,6 +247,7 @@ public class SystemTable
int gen = Math.max(FBUtilities.byteArrayToInt(generation.value()) + 1,
(int) (System.currentTimeMillis() / 1000));
IColumn cluster = cf.getColumn(CLUSTERNAME);
+ IColumn partitionerColumn = cf.getColumn(PARTITIONER);
RowMutation rm = new RowMutation(Table.SYSTEM_TABLE, LOCATION_KEY);
cf = ColumnFamily.create(Table.SYSTEM_TABLE, SystemTable.STATUS_CF);
@@ -186,8 +266,29 @@ public class SystemTable
cname = DatabaseDescriptor.getClusterName().getBytes();
logger.info("Saved ClusterName not found. Using " +
DatabaseDescriptor.getClusterName());
}
+
+ if (partitionerColumn == null)
+ {
+ Column c = new Column(PARTITIONER, partitioner.getBytes("UTF-8"));
+ cf.addColumn(c);
+ logger.info("Saved partitioner not found. Using " + partitioner);
+ }
+
rm.add(cf);
rm.apply();
+ try
+ {
+
table.getColumnFamilyStore(SystemTable.STATUS_CF).forceBlockingFlush();
+ }
+ catch (ExecutionException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+
metadata = new StorageMetadata(token, gen, cname);
return metadata;
}
Modified:
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/thrift/CassandraDaemon.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/thrift/CassandraDaemon.java?rev=952219&r1=952218&r2=952219&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/thrift/CassandraDaemon.java
(original)
+++
cassandra/branches/cassandra-0.6/src/java/org/apache/cassandra/thrift/CassandraDaemon.java
Mon Jun 7 13:38:18 2010
@@ -27,6 +27,7 @@ import java.util.concurrent.SynchronousQ
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import org.apache.cassandra.db.SystemTable;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
@@ -86,7 +87,18 @@ public class CassandraDaemon
}
}
});
-
+
+ // check the system table for mismatched partitioner.
+ try
+ {
+ SystemTable.checkHealth();
+ }
+ catch (IOException e)
+ {
+ logger.error("Fatal exception during initialization", e);
+ System.exit(100);
+ }
+
// initialize keyspaces
for (String table : DatabaseDescriptor.getTables())
{
Modified:
cassandra/branches/cassandra-0.6/test/unit/org/apache/cassandra/service/MoveTest.java
URL:
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.6/test/unit/org/apache/cassandra/service/MoveTest.java?rev=952219&r1=952218&r2=952219&view=diff
==============================================================================
---
cassandra/branches/cassandra-0.6/test/unit/org/apache/cassandra/service/MoveTest.java
(original)
+++
cassandra/branches/cassandra-0.6/test/unit/org/apache/cassandra/service/MoveTest.java
Mon Jun 7 13:38:18 2010
@@ -26,6 +26,7 @@ import java.net.UnknownHostException;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
+import org.apache.cassandra.CleanupHelper;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
@@ -43,7 +44,7 @@ import org.apache.cassandra.locator.Rack
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.gms.ApplicationState;
-public class MoveTest
+public class MoveTest extends CleanupHelper
{
// handy way of creating a mapping of strategies to use in StorageService.
private static Map<String, AbstractReplicationStrategy>
createReplacements(AbstractReplicationStrategy strat)