diff --git a/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/InsufficientConsistencyException.java b/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/InsufficientConsistencyException.java index 348ac44c671..330370104a1 100644 --- a/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/InsufficientConsistencyException.java +++ b/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/InsufficientConsistencyException.java @@ -15,13 +15,13 @@ */ package com.palantir.atlasdb.keyvalue.api; -import com.palantir.common.exception.PalantirRuntimeException; +import com.palantir.common.exception.AtlasDbDependencyException; /** * Thrown by a key value service when an operation could not be performed * because the required consistency could not be met. */ -public class InsufficientConsistencyException extends PalantirRuntimeException { +public class InsufficientConsistencyException extends AtlasDbDependencyException { private static final long serialVersionUID = 1L; public InsufficientConsistencyException(String msg) { diff --git a/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/KeyAlreadyExistsException.java b/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/KeyAlreadyExistsException.java index 213314f11ee..3b1e588908e 100644 --- a/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/KeyAlreadyExistsException.java +++ b/atlasdb-api/src/main/java/com/palantir/atlasdb/keyvalue/api/KeyAlreadyExistsException.java @@ -18,9 +18,9 @@ import java.util.Collection; import com.google.common.collect.ImmutableList; -import com.palantir.common.exception.PalantirRuntimeException; +import com.palantir.common.exception.AtlasDbDependencyException; -public class KeyAlreadyExistsException extends PalantirRuntimeException { +public class KeyAlreadyExistsException extends AtlasDbDependencyException { private static final long serialVersionUID = 1L; private final ImmutableList existingKeys; diff --git a/atlasdb-api/src/main/java/com/palantir/atlasdb/transaction/api/TransactionManager.java b/atlasdb-api/src/main/java/com/palantir/atlasdb/transaction/api/TransactionManager.java index 13662946441..e8421b78961 100644 --- a/atlasdb-api/src/main/java/com/palantir/atlasdb/transaction/api/TransactionManager.java +++ b/atlasdb-api/src/main/java/com/palantir/atlasdb/transaction/api/TransactionManager.java @@ -15,13 +15,15 @@ */ package com.palantir.atlasdb.transaction.api; +import com.palantir.exception.NotInitializedException; + public interface TransactionManager extends AutoCloseable { /** * Whether this transaction manager has established a connection to the backing store and timestamp/lock services, * and is ready to service transactions. * * If an attempt is made to execute a transaction when this method returns {@code false}, a - * {@link com.palantir.exception.NotInitializedException} will be thrown. + * {@link NotInitializedException} will be thrown. * * This method is used for TransactionManagers that can be initialized asynchronously (i.e. those extending * {@link com.palantir.async.initializer.AsyncInitializer}; other TransactionManagers can keep the default diff --git a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownDeleteTest.java b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownDeleteTest.java index 58a15a9a97b..f5f10140aea 100644 --- a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownDeleteTest.java +++ b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownDeleteTest.java @@ -20,7 +20,7 @@ import org.junit.Test; import com.google.common.collect.ImmutableMultimap; -import com.palantir.common.exception.PalantirRuntimeException; +import com.palantir.common.exception.AtlasDbDependencyException; public class OneNodeDownDeleteTest { @@ -28,6 +28,6 @@ public class OneNodeDownDeleteTest { public void deletingThrows() { assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.delete(OneNodeDownTestSuite.TEST_TABLE, ImmutableMultimap.of(OneNodeDownTestSuite.CELL_1_1, OneNodeDownTestSuite.DEFAULT_TIMESTAMP))) - .isInstanceOf(PalantirRuntimeException.class); + .isInstanceOf(AtlasDbDependencyException.class); } } diff --git a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownGetTest.java b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownGetTest.java index df63fdfe302..f92d3c4b146 100644 --- a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownGetTest.java +++ b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownGetTest.java @@ -41,7 +41,6 @@ import com.palantir.atlasdb.keyvalue.api.RowResult; import com.palantir.atlasdb.keyvalue.api.Value; import com.palantir.common.base.ClosableIterator; -import com.palantir.common.exception.PalantirRuntimeException; public class OneNodeDownGetTest { @@ -120,6 +119,7 @@ public void getRangeOfTimestampsThrows() { public void getAllTimestampsThrows() { assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.getAllTimestamps(OneNodeDownTestSuite.TEST_TABLE, ImmutableSet.of(OneNodeDownTestSuite.CELL_1_1), Long.MAX_VALUE)) - .isExactlyInstanceOf(PalantirRuntimeException.class); + .isExactlyInstanceOf(InsufficientConsistencyException.class) + .hasMessage("This get operation requires ALL Cassandra nodes to be up and available."); } } diff --git a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownMetadataTest.java b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownMetadataTest.java index b80f6aeac7e..2e973de20ce 100644 --- a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownMetadataTest.java +++ b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownMetadataTest.java @@ -29,6 +29,7 @@ import com.palantir.atlasdb.table.description.NameMetadataDescription; import com.palantir.atlasdb.table.description.TableMetadata; import com.palantir.atlasdb.transaction.api.ConflictHandler; +import com.palantir.common.exception.AtlasDbDependencyException; public class OneNodeDownMetadataTest { @@ -51,8 +52,10 @@ public void putMetadataForTableThrows() { TableMetadata newTableMetadata = new TableMetadata(new NameMetadataDescription(), new ColumnMetadataDescription(), ConflictHandler.IGNORE_ALL); assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.putMetadataForTable(OneNodeDownTestSuite.TEST_TABLE, - newTableMetadata.persistToBytes())).isExactlyInstanceOf(IllegalStateException.class) - .hasMessageContaining("At schema version UNREACHABLE"); + newTableMetadata.persistToBytes())) + .isExactlyInstanceOf(AtlasDbDependencyException.class) + .hasCauseInstanceOf(IllegalStateException.class) + .hasStackTraceContaining("At schema version UNREACHABLE"); canGetMetadataForTable(); } @@ -63,8 +66,9 @@ public void putMetadataForTablesThrows() { new ColumnMetadataDescription(), ConflictHandler.IGNORE_ALL); assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.putMetadataForTables( ImmutableMap.of(OneNodeDownTestSuite.TEST_TABLE, newTableMetadata.persistToBytes()))) - .isExactlyInstanceOf(IllegalStateException.class) - .hasMessageContaining("At schema version UNREACHABLE"); + .isExactlyInstanceOf(AtlasDbDependencyException.class) + .hasCauseInstanceOf(IllegalStateException.class) + .hasStackTraceContaining("At schema version UNREACHABLE"); canGetMetadataForTable(); } diff --git a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownTableManipulationTest.java b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownTableManipulationTest.java index 9d3a03559b3..eeeca16d07e 100644 --- a/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownTableManipulationTest.java +++ b/atlasdb-cassandra-multinode-tests/src/test/java/com/palantir/cassandra/multinode/OneNodeDownTableManipulationTest.java @@ -23,8 +23,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.palantir.atlasdb.AtlasDbConstants; +import com.palantir.atlasdb.keyvalue.api.InsufficientConsistencyException; import com.palantir.atlasdb.keyvalue.api.TableReference; -import com.palantir.common.exception.PalantirRuntimeException; +import com.palantir.common.exception.AtlasDbDependencyException; public class OneNodeDownTableManipulationTest { private static final TableReference NEW_TABLE = TableReference.createWithEmptyNamespace("new_table"); @@ -54,7 +55,8 @@ public void canCreateTables() { public void dropTableThrows() { assertThat(OneNodeDownTestSuite.kvs.getAllTableNames()).contains(OneNodeDownTestSuite.TEST_TABLE_TO_DROP); assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.dropTable(OneNodeDownTestSuite.TEST_TABLE_TO_DROP)) - .isExactlyInstanceOf(IllegalStateException.class); + .isExactlyInstanceOf(AtlasDbDependencyException.class) + .hasCauseInstanceOf(IllegalStateException.class); // This documents and verifies the current behaviour, dropping the table in spite of the exception // Seems to be inconsistent with the API assertThat(OneNodeDownTestSuite.kvs.getAllTableNames()).doesNotContain(OneNodeDownTestSuite.TEST_TABLE_TO_DROP); @@ -65,7 +67,8 @@ public void dropTablesThrows() { assertThat(OneNodeDownTestSuite.kvs.getAllTableNames()).contains(OneNodeDownTestSuite.TEST_TABLE_TO_DROP_2); assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.dropTables( ImmutableSet.of(OneNodeDownTestSuite.TEST_TABLE_TO_DROP_2))) - .isExactlyInstanceOf(IllegalStateException.class); + .isExactlyInstanceOf(AtlasDbDependencyException.class) + .hasCauseInstanceOf(IllegalStateException.class); // This documents and verifies the current behaviour, dropping the table in spite of the exception // Seems to be inconsistent with the API assertThat(OneNodeDownTestSuite.kvs.getAllTableNames()) @@ -85,7 +88,7 @@ public void canCleanUpSchemaMutationLockTablesState() throws Exception { @Test public void truncateTableThrows() { assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.truncateTable(OneNodeDownTestSuite.TEST_TABLE)) - .isExactlyInstanceOf(PalantirRuntimeException.class) + .isExactlyInstanceOf(InsufficientConsistencyException.class) .hasMessage("Truncating tables requires all Cassandra nodes to be up and available."); } @@ -93,7 +96,7 @@ public void truncateTableThrows() { public void truncateTablesThrows() { assertThatThrownBy(() -> OneNodeDownTestSuite.kvs.truncateTables( ImmutableSet.of(OneNodeDownTestSuite.TEST_TABLE))) - .isExactlyInstanceOf(PalantirRuntimeException.class) + .isExactlyInstanceOf(InsufficientConsistencyException.class) .hasMessage("Truncating tables requires all Cassandra nodes to be up and available."); } } diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraClientFactory.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraClientFactory.java index a0e5c8bf22b..c43960dfc18 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraClientFactory.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraClientFactory.java @@ -46,6 +46,7 @@ import com.google.common.collect.Maps; import com.palantir.atlasdb.cassandra.CassandraCredentialsConfig; import com.palantir.atlasdb.cassandra.CassandraKeyValueServiceConfig; +import com.palantir.common.exception.AtlasDbDependencyException; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.UnsafeArg; import com.palantir.remoting3.config.ssl.SslSocketFactories; @@ -183,7 +184,7 @@ public void destroyObject(PooledObject client) { SafeArg.of("cassandraClient", CassandraLogHelper.host(addr))); } - static class ClientCreationFailedException extends RuntimeException { + static class ClientCreationFailedException extends AtlasDbDependencyException { private static final long serialVersionUID = 1L; ClientCreationFailedException(String message, Exception cause) { diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraKeyValueServiceImpl.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraKeyValueServiceImpl.java index d834a3745ad..cfdf0b7b871 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraKeyValueServiceImpl.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CassandraKeyValueServiceImpl.java @@ -478,7 +478,7 @@ public String toString() { } return ImmutableMap.copyOf(result); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -548,7 +548,7 @@ public Map get(TableReference tableRef, Map timestampBy } return builder.build(); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -771,7 +771,7 @@ private Map getRowsColumnRangeIteratorForSingleH } return ret; } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -809,7 +809,7 @@ public String toString() { } }); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -950,7 +950,7 @@ public void put(final TableReference tableRef, final Map values, f try { putInternal(tableRef, KeyValueServices.toConstantTimestampValues(values.entrySet(), timestamp)); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -970,7 +970,7 @@ public void putWithTimestamps(TableReference tableRef, Multimap val try { putInternal(tableRef, values.entries()); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -1183,10 +1183,16 @@ private Void batchMutateInternal(Client client, Set tableRefs, Map>> map, ConsistencyLevel consistency) throws TException { - return queryRunner.run(client, tableRefs, () -> { - client.batch_mutate(map, consistency); - return null; - }); + try { + return queryRunner.run(client, tableRefs, () -> { + client.batch_mutate(map, consistency); + return null; + }); + } catch (UnavailableException e) { + throw new InsufficientConsistencyException( + "This batch mutate operation requires " + consistency + " Cassandra nodes to be up and available.", + e); + } } private Map> multigetInternal( @@ -1196,7 +1202,12 @@ private Map> multigetInternal( ColumnParent colFam, SlicePredicate pred, ConsistencyLevel consistency) throws TException { - return queryRunner.run(client, tableRef, () -> client.multiget_slice(rowNames, colFam, pred, consistency)); + try { + return queryRunner.run(client, tableRef, () -> client.multiget_slice(rowNames, colFam, pred, consistency)); + } catch (UnavailableException e) { + throw new InsufficientConsistencyException( + "This get operation requires " + consistency + " Cassandra nodes to be up and available.", e); + } } /** @@ -1235,10 +1246,10 @@ public void truncateTables(final Set tablesToTruncate) { try { runTruncateInternal(tablesToTruncate); } catch (UnavailableException e) { - throw new PalantirRuntimeException("Truncating tables requires all Cassandra nodes" + throw new InsufficientConsistencyException("Truncating tables requires all Cassandra nodes" + " to be up and available."); } catch (TException e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } } @@ -1367,9 +1378,10 @@ public String toString() { } }); } catch (UnavailableException e) { - throw new PalantirRuntimeException("Deleting requires all Cassandra nodes to be up and available."); + throw new InsufficientConsistencyException("Deleting requires all Cassandra nodes to be up and available.", + e); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -1550,7 +1562,7 @@ public void dropTables(final Set tablesToDrop) { schemaMutationLock.runWithLock(() -> dropTablesInternal(tablesToDrop)); } - private void dropTablesInternal(final Set tablesToDrop) throws Exception { + private void dropTablesInternal(final Set tablesToDrop) { try { clientPool.runWithRetry((FunctionCheckedException) client -> { KsDef ks = client.describe_keyspace(configManager.getConfig().getKeyspaceOrThrow()); @@ -1577,7 +1589,10 @@ private void dropTablesInternal(final Set tablesToDrop) throws E return null; }); } catch (UnavailableException e) { - throw new PalantirRuntimeException("Dropping tables requires all Cassandra nodes to be up and available."); + throw new InsufficientConsistencyException( + "Dropping tables requires all Cassandra nodes to be up and available.", e); + } catch (Exception e) { + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -1703,7 +1718,7 @@ private Map filterOutExistingTables( } } } catch (Exception e) { - Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } return filteredTables; @@ -1719,12 +1734,12 @@ private void createTablesInternal(final Map tableNamesTo configManager.getConfig().gcGraceSeconds(), tableEntry.getValue())); } catch (UnavailableException e) { - throw new PalantirRuntimeException( + throw new InsufficientConsistencyException( "Creating tables requires all Cassandra nodes to be up and available."); } catch (TException thriftException) { if (thriftException.getMessage() != null && !thriftException.getMessage().contains("already existing table")) { - Throwables.unwrapAndThrowUncheckedException(thriftException); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(thriftException); } } } @@ -1966,7 +1981,7 @@ private void putMetadataAndMaybeAlterTables( return null; }); } catch (Exception e) { - Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -2030,7 +2045,7 @@ public void addGarbageCollectionSentinelValues(TableReference tableRef, Iterable final Value value = Value.create(PtBytes.EMPTY_BYTE_ARRAY, Value.INVALID_VALUE_TIMESTAMP); putInternal(tableRef, Iterables.transform(cells, cell -> Maps.immutableEntry(cell, value))); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -2087,7 +2102,7 @@ public void putUnlessExists(final TableReference tableRef, final Map oldColumns; - java.util.Optional oldValue = request.oldValue(); - if (oldValue.isPresent()) { - oldColumns = ImmutableList.of(makeColumn(colName, oldValue.get(), timestamp)); - } else { - oldColumns = ImmutableList.of(); - } + try { + TableReference table = request.table(); + Cell cell = request.cell(); + long timestamp = AtlasDbConstants.TRANSACTION_TS; - Column newColumn = makeColumn(colName, request.newValue(), timestamp); - return queryRunner.run(client, table, () -> client.cas( - rowName, - internalTableName(table), - oldColumns, - ImmutableList.of(newColumn), - ConsistencyLevel.SERIAL, - writeConsistency)); + ByteBuffer rowName = ByteBuffer.wrap(cell.getRowName()); + byte[] colName = CassandraKeyValueServices + .makeCompositeBuffer(cell.getColumnName(), timestamp) + .array(); + + List oldColumns; + java.util.Optional oldValue = request.oldValue(); + if (oldValue.isPresent()) { + oldColumns = ImmutableList.of(makeColumn(colName, oldValue.get(), timestamp)); + } else { + oldColumns = ImmutableList.of(); + } + + Column newColumn = makeColumn(colName, request.newValue(), timestamp); + return queryRunner.run(client, table, () -> client.cas( + rowName, + internalTableName(table), + oldColumns, + ImmutableList.of(newColumn), + ConsistencyLevel.SERIAL, + writeConsistency)); + } catch (UnavailableException e) { + throw new InsufficientConsistencyException( + "Check-and-set requires " + writeConsistency + " Cassandra nodes to be up and available.", e); + } } private Column makeColumn(byte[] colName, byte[] contents, long timestamp) { @@ -2347,7 +2367,7 @@ public CassandraTables getCassandraTables() { * Does not require all Cassandra nodes to be up and available, works as long as quorum is achieved. */ @Override - public void cleanUpSchemaMutationLockTablesState() throws Exception { + public void cleanUpSchemaMutationLockTablesState() throws TException { Set tables = lockTables.getAllLockTables(); java.util.Optional tableToKeep = tables.stream().findFirst(); if (!tableToKeep.isPresent()) { @@ -2407,7 +2427,7 @@ private List runAllTasksCancelOnFailure(List> tasks) { //Callable returns null, so can't use immutable list return Collections.singletonList(tasks.get(0).call()); } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -2422,7 +2442,7 @@ private List runAllTasksCancelOnFailure(List> tasks) { } return results; } catch (Exception e) { - throw Throwables.unwrapAndThrowUncheckedException(e); + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } finally { for (Future future : futures) { future.cancel(true); diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CqlExecutor.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CqlExecutor.java index 99985b630d8..2ac44f7ec4f 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CqlExecutor.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/CqlExecutor.java @@ -185,7 +185,7 @@ private void logResultSize(KvsProfilingLogger.LoggingFunction log, CqlResult res @Override public String toString() { - return String.format(queryFormat, queryArgs); + return String.format(queryFormat, (Object[]) queryArgs); } } diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/Heartbeat.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/Heartbeat.java index bc0824a0057..73efdbc2b8b 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/Heartbeat.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/Heartbeat.java @@ -21,11 +21,13 @@ import org.apache.cassandra.thrift.Cassandra.Client; import org.apache.cassandra.thrift.Column; import org.apache.cassandra.thrift.ConsistencyLevel; +import org.apache.cassandra.thrift.UnavailableException; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; +import com.palantir.atlasdb.keyvalue.api.InsufficientConsistencyException; import com.palantir.atlasdb.keyvalue.api.TableReference; import com.palantir.common.base.Throwables; @@ -89,14 +91,19 @@ private Void beat(Client client) throws TException { } private CASResult writeDdlLockWithCas(Client client, Column ourUpdate, List expected) throws TException { - return queryRunner.run(client, lockTable, - () -> client.cas( - SchemaMutationLock.getGlobalDdlLockRowName(), - lockTable.getQualifiedName(), - expected, - ImmutableList.of(ourUpdate), - ConsistencyLevel.SERIAL, - writeConsistency)); + try { + return queryRunner.run(client, lockTable, + () -> client.cas( + SchemaMutationLock.getGlobalDdlLockRowName(), + lockTable.getQualifiedName(), + expected, + ImmutableList.of(ourUpdate), + ConsistencyLevel.SERIAL, + writeConsistency)); + } catch (UnavailableException e) { + throw new InsufficientConsistencyException( + "CAS for the heartbeat requires " + writeConsistency + "Cassandra nodes to be available.", e); + } } @Override diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/SchemaMutationLock.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/SchemaMutationLock.java index b2a01b43d05..aa3e1f9e206 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/SchemaMutationLock.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/SchemaMutationLock.java @@ -33,6 +33,7 @@ import org.apache.cassandra.thrift.ColumnPath; import org.apache.cassandra.thrift.ConsistencyLevel; import org.apache.cassandra.thrift.NotFoundException; +import org.apache.cassandra.thrift.UnavailableException; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +46,7 @@ import com.google.common.primitives.Longs; import com.palantir.atlasdb.AtlasDbConstants; import com.palantir.atlasdb.cassandra.CassandraKeyValueServiceConfigManager; +import com.palantir.atlasdb.keyvalue.api.InsufficientConsistencyException; import com.palantir.atlasdb.keyvalue.api.TableReference; import com.palantir.common.base.FunctionCheckedException; import com.palantir.common.base.Throwables; @@ -346,10 +348,14 @@ private Optional queryExistingLockColumn(Client client) throws TExceptio ColumnPath columnPath = new ColumnPath(lockTableRef.getQualifiedName()); columnPath.setColumn(getGlobalDdlLockColumnName()); Column existingColumn = null; + ConsistencyLevel localQuorum = ConsistencyLevel.LOCAL_QUORUM; try { ColumnOrSuperColumn result = queryRunner.run(client, lockTableRef, - () -> client.get(getGlobalDdlLockRowName(), columnPath, ConsistencyLevel.LOCAL_QUORUM)); + () -> client.get(getGlobalDdlLockRowName(), columnPath, localQuorum)); existingColumn = result.getColumn(); + } catch (UnavailableException e) { + throw new InsufficientConsistencyException( + "Checking the schema lock requires " + localQuorum + " Cassandra nodes to be up and available.", e); } catch (NotFoundException e) { log.debug("No existing schema lock found in table [{}]", SafeArg.of("tableName", lockTableRef)); } diff --git a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/paging/RowGetter.java b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/paging/RowGetter.java index 6dcf07a1ca3..9418ba57557 100644 --- a/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/paging/RowGetter.java +++ b/atlasdb-cassandra/src/main/java/com/palantir/atlasdb/keyvalue/cassandra/paging/RowGetter.java @@ -32,6 +32,7 @@ import com.palantir.atlasdb.keyvalue.cassandra.CassandraKeyValueServiceImpl; import com.palantir.atlasdb.keyvalue.cassandra.TracingQueryRunner; import com.palantir.common.base.FunctionCheckedException; +import com.palantir.common.base.Throwables; public class RowGetter { private CassandraClientPool clientPool; @@ -62,12 +63,10 @@ public List apply(Cassandra.Client client) throws Exception { return queryRunner.run(client, tableRef, () -> client.get_range_slices(colFam, slicePredicate, keyRange, consistency)); } catch (UnavailableException e) { - if (consistency.equals(ConsistencyLevel.ALL)) { - throw new InsufficientConsistencyException("This operation requires all Cassandra" - + " nodes to be up and available.", e); - } else { - throw e; - } + throw new InsufficientConsistencyException("get_range_slices requires " + consistency + + " Cassandra nodes to be up and available.", e); + } catch (Exception e) { + throw Throwables.unwrapAndThrowAtlasDbDependencyException(e); } } @@ -77,5 +76,4 @@ public String toString() { } }); } - } diff --git a/atlasdb-commons/src/main/java/com/palantir/common/base/Throwables.java b/atlasdb-commons/src/main/java/com/palantir/common/base/Throwables.java index ff689fd57ff..044baca7b67 100644 --- a/atlasdb-commons/src/main/java/com/palantir/common/base/Throwables.java +++ b/atlasdb-commons/src/main/java/com/palantir/common/base/Throwables.java @@ -29,8 +29,8 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; +import com.palantir.common.exception.AtlasDbDependencyException; import com.palantir.common.exception.PalantirRuntimeException; -import com.palantir.exception.PalantirInterruptedException; public final class Throwables { @@ -80,14 +80,23 @@ public static RuntimeException rewrapAndThrowUncheckedException(String newMessag } /** - * If Throwable is a RuntimeException or Error, rethrow it. If not, throw a + * If Throwable is a RuntimeException or Error, rethrow it. If its an ExecutionException or + * InvocationTargetException, extract the cause and process it. Else, throw a * new PalantirRuntimeException(ex) */ - public static RuntimeException unwrapAndThrowUncheckedException(Throwable ex) { - if (isInstance(ex, ExecutionException.class) || isInstance(ex, InvocationTargetException.class)) { - throwUncheckedException(ex.getCause()); + public static AtlasDbDependencyException unwrapAndThrowAtlasDbDependencyException(Throwable ex) { + if (ex instanceof ExecutionException || ex instanceof InvocationTargetException) { + throw createAtlasDbDependencyException(ex.getCause()); } - throw throwUncheckedException(ex); + throw createAtlasDbDependencyException(ex); + } + + private static RuntimeException createAtlasDbDependencyException(Throwable ex) { + if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + throwIfInstance(ex, AtlasDbDependencyException.class); + return new AtlasDbDependencyException(ex); } public static RuntimeException throwUncheckedException(Throwable ex) { @@ -103,10 +112,8 @@ private static RuntimeException createPalantirRuntimeException(Throwable ex) { private static RuntimeException createPalantirRuntimeException(String newMessage, Throwable ex) { if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) { Thread.currentThread().interrupt(); - return new PalantirInterruptedException(newMessage, ex); - } else { - return new PalantirRuntimeException(newMessage, ex); } + return new PalantirRuntimeException(newMessage, ex); } /** diff --git a/atlasdb-commons/src/main/java/com/palantir/common/exception/AtlasDbDependencyException.java b/atlasdb-commons/src/main/java/com/palantir/common/exception/AtlasDbDependencyException.java new file mode 100644 index 00000000000..d4ae43a500c --- /dev/null +++ b/atlasdb-commons/src/main/java/com/palantir/common/exception/AtlasDbDependencyException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the BSD-3 License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.common.exception; + +public class AtlasDbDependencyException extends RuntimeException { + private static final String EXCEPTION_MESSAGE = "AtlasDB dependency threw an exception."; + + public AtlasDbDependencyException(String msg) { + super(msg); + } + + public AtlasDbDependencyException(String msg, Throwable throwable) { + super(msg, throwable); + } + + public AtlasDbDependencyException(Throwable throwable) { + super(EXCEPTION_MESSAGE, throwable); + } +} diff --git a/atlasdb-commons/src/main/java/com/palantir/exception/NotInitializedException.java b/atlasdb-commons/src/main/java/com/palantir/exception/NotInitializedException.java index d94f603b18a..865215c3bd0 100644 --- a/atlasdb-commons/src/main/java/com/palantir/exception/NotInitializedException.java +++ b/atlasdb-commons/src/main/java/com/palantir/exception/NotInitializedException.java @@ -16,9 +16,10 @@ package com.palantir.exception; +import com.palantir.common.exception.AtlasDbDependencyException; import com.palantir.logsafe.SafeArg; -public class NotInitializedException extends RuntimeException { +public class NotInitializedException extends AtlasDbDependencyException { public NotInitializedException(String objectNotInitialized) { super(String.format("The %s is not initialized yet", SafeArg.of("objectName", objectNotInitialized))); } diff --git a/atlasdb-config/src/main/java/com/palantir/atlasdb/config/AtlasDbConfig.java b/atlasdb-config/src/main/java/com/palantir/atlasdb/config/AtlasDbConfig.java index 1a70770909b..6161854ad4c 100644 --- a/atlasdb-config/src/main/java/com/palantir/atlasdb/config/AtlasDbConfig.java +++ b/atlasdb-config/src/main/java/com/palantir/atlasdb/config/AtlasDbConfig.java @@ -28,6 +28,7 @@ import com.palantir.atlasdb.AtlasDbConstants; import com.palantir.atlasdb.memory.InMemoryAtlasDbConfig; import com.palantir.atlasdb.spi.KeyValueServiceConfig; +import com.palantir.exception.NotInitializedException; @JsonDeserialize(as = ImmutableAtlasDbConfig.class) @JsonSerialize(as = ImmutableAtlasDbConfig.class) @@ -150,7 +151,7 @@ public int getBackgroundScrubBatchSize() { * If true, initialization will be attempted synchronously, but on failure we keep retrying asynchronously to start * AtlasDB. If a method is invoked on an not-yet-initialized * {@link com.palantir.atlasdb.transaction.api.TransactionManager} or other object, a - * {@link com.palantir.exception.NotInitializedException} will be thrown. Clients can register a + * {@link NotInitializedException} will be thrown. Clients can register a * {@link com.palantir.atlasdb.http.NotInitializedExceptionMapper} if they wish to map this exception to a 503 * status code. */ diff --git a/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/keyvalue/impl/AbstractKeyValueServiceTest.java b/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/keyvalue/impl/AbstractKeyValueServiceTest.java index d4149cbac41..09228bfcf21 100644 --- a/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/keyvalue/impl/AbstractKeyValueServiceTest.java +++ b/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/keyvalue/impl/AbstractKeyValueServiceTest.java @@ -15,6 +15,7 @@ */ package com.palantir.atlasdb.keyvalue.impl; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; @@ -83,6 +84,7 @@ import com.palantir.atlasdb.table.description.ValueType; import com.palantir.atlasdb.transaction.api.ConflictHandler; import com.palantir.common.base.ClosableIterator; +import com.palantir.common.exception.AtlasDbDependencyException; public abstract class AbstractKeyValueServiceTest { protected static final TableReference TEST_TABLE = TableReference.createFromFullyQualifiedName("ns.pt_kvs_test"); @@ -1149,9 +1151,10 @@ public void testKeyAlreadyExists() { putTestDataForSingleTimestamp(); try { putTestDataForSingleTimestamp(); - // Legal - } catch (KeyAlreadyExistsException e) { - Assert.fail("Must not throw when overwriting with same value!"); + } catch (AtlasDbDependencyException e) { + if (KeyAlreadyExistsException.class.isInstance(e.getCause())) { + Assert.fail("Must not throw when overwriting with same value!"); + } } keyValueService.putWithTimestamps( @@ -1165,16 +1168,17 @@ public void testKeyAlreadyExists() { ImmutableMultimap.of( TEST_CELL, Value.create(value00, TEST_TIMESTAMP + 1))); - // Legal - } catch (KeyAlreadyExistsException e) { - Assert.fail("Must not throw when overwriting with same value!"); + } catch (AtlasDbDependencyException e) { + if (KeyAlreadyExistsException.class.isInstance(e.getCause())) { + Assert.fail("Must not throw when overwriting with same value!"); + } } try { keyValueService.putWithTimestamps(TEST_TABLE, ImmutableMultimap.of( TEST_CELL, Value.create(value01, TEST_TIMESTAMP + 1))); // Legal - } catch (KeyAlreadyExistsException e) { + } catch (AtlasDbDependencyException e) { // Legal } @@ -1182,16 +1186,13 @@ public void testKeyAlreadyExists() { try { keyValueService.putUnlessExists(TEST_TABLE, ImmutableMap.of(TEST_CELL, value00)); // Legal - } catch (KeyAlreadyExistsException e) { + } catch (AtlasDbDependencyException e) { // Legal } - try { - keyValueService.putUnlessExists(TEST_TABLE, ImmutableMap.of(TEST_CELL, value00)); - Assert.fail("putUnlessExists must throw when overwriting the same cell!"); - } catch (KeyAlreadyExistsException e) { - // Legal - } + assertThatThrownBy(() -> keyValueService.putUnlessExists(TEST_TABLE, ImmutableMap.of(TEST_CELL, value00))) + .isInstanceOf(AtlasDbDependencyException.class) + .as("putUnlessExists must throw when overwriting the same cell!"); } @Test diff --git a/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/transaction/impl/AbstractTransactionTest.java b/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/transaction/impl/AbstractTransactionTest.java index b0eeffcf0b5..ec0aa7b7592 100644 --- a/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/transaction/impl/AbstractTransactionTest.java +++ b/atlasdb-tests-shared/src/main/java/com/palantir/atlasdb/transaction/impl/AbstractTransactionTest.java @@ -15,6 +15,7 @@ */ package com.palantir.atlasdb.transaction.impl; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -57,7 +58,6 @@ import com.palantir.atlasdb.keyvalue.api.BatchColumnRangeSelection; import com.palantir.atlasdb.keyvalue.api.Cell; import com.palantir.atlasdb.keyvalue.api.ColumnSelection; -import com.palantir.atlasdb.keyvalue.api.KeyAlreadyExistsException; import com.palantir.atlasdb.keyvalue.api.RangeRequest; import com.palantir.atlasdb.keyvalue.api.RangeRequests; import com.palantir.atlasdb.keyvalue.api.RowResult; @@ -80,6 +80,7 @@ import com.palantir.common.base.Throwables; import com.palantir.common.collect.IterableView; import com.palantir.common.collect.MapEntries; +import com.palantir.common.exception.AtlasDbDependencyException; import com.palantir.lock.impl.LegacyTimelockService; import com.palantir.util.Pair; import com.palantir.util.paging.TokenBackedBasicResultsPage; @@ -171,13 +172,11 @@ public void testPrimaryKeyViolation() { Cell cell = Cell.create("r1".getBytes(), TransactionConstants.COMMIT_TS_COLUMN); keyValueService.putUnlessExists(TransactionConstants.TRANSACTION_TABLE, ImmutableMap.of(cell, "v1".getBytes())); - try { - keyValueService.putUnlessExists(TransactionConstants.TRANSACTION_TABLE, - ImmutableMap.of(cell, "v2".getBytes())); - fail(); - } catch (KeyAlreadyExistsException e) { - //expected - } + + assertThatThrownBy(() -> + keyValueService.putUnlessExists(TransactionConstants.TRANSACTION_TABLE, + ImmutableMap.of(cell, "v2".getBytes()))) + .isInstanceOf(AtlasDbDependencyException.class); } @Test diff --git a/docs/source/release_notes/release-notes.rst b/docs/source/release_notes/release-notes.rst index 303899307b2..a0c48fe9dac 100644 --- a/docs/source/release_notes/release-notes.rst +++ b/docs/source/release_notes/release-notes.rst @@ -48,6 +48,11 @@ develop * - Type - Change + * - |improved| |devbreak| + - AtlasDB will now consistently throw a ``InsufficientConsistencyException`` if Cassandra reports an ``UnavailableException``. + Also, all Cassandra KVS exceptions like ``KeyAlreadyExists`` or ``TTransportException`` as well as ``NotInitializedException`` will get wrapped into ``AtlasDbDependencyException`` in the interest of consistent exceptions. + (`Pull Request `__) + * - |fixed| - Reverted the Cassandra KVS executor PR (`Pull Request `__) that caused a performance regression. (`Pull Request `__)