This is an automated email from the ASF dual-hosted git repository.
edimitrova pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 50978a0d07 CASSANDRA-20402: Add new reason
RequestFailureReason.INDEX_BUILD_IN_PROGRESS and IndexBuildInProgress exception
when queries fail during index build
50978a0d07 is described below
commit 50978a0d0738327290d06288c78967a61643506b
Author: Ekaterina Dimitrova <[email protected]>
AuthorDate: Thu Dec 5 16:28:08 2024 -0500
CASSANDRA-20402: Add new reason RequestFailureReason.INDEX_BUILD_IN_PROGRESS
and IndexBuildInProgress exception when queries fail during index build
patch by Ekaterina Dimitrova; reviewed by Caleb Rackliffe for
CASSANDRA-20402
---
CHANGES.txt | 1 +
.../cassandra/exceptions/RequestFailureReason.java | 58 ++++++++--------
.../index/IndexBuildInProgressException.java | 36 ++++++++++
.../apache/cassandra/index/IndexStatusManager.java | 12 +++-
.../cassandra/index/SecondaryIndexManager.java | 15 +++-
src/java/org/apache/cassandra/net/InboundSink.java | 14 +++-
.../test/sai/IndexAvailabilityTest.java | 80 +++++++++++++++++++++-
.../validation/entities/SecondaryIndexTest.java | 13 ++--
.../exceptions/RequestFailureReasonTest.java | 43 +++++++++++-
.../cassandra/index/IndexStatusManagerTest.java | 2 +-
.../index/sai/cql/AllowFilteringTest.java | 32 +++++++++
11 files changed, 267 insertions(+), 39 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 029a87ae29..c03ffcafa4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
5.1
+ * Throw new IndexBuildInProgressException when queries fail during index
build, instead of IndexNotAvailableException (CASSANDRA-20402)
* Fix Paxos repair interrupts running transactions (CASSANDRA-20469)
* Various fixes in constraint framework (CASSANDRA-20481)
* Add support in CAS for -= on numeric types, and fixed improper handling of
empty bytes which lead to NPE (CASSANDRA-20477)
diff --git a/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java
b/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java
index 1bc86ff061..9faff584f1 100644
--- a/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java
+++ b/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java
@@ -18,15 +18,18 @@
package org.apache.cassandra.exceptions;
import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.cassandra.db.filter.TombstoneOverwhelmingException;
+import org.apache.cassandra.index.IndexBuildInProgressException;
+import org.apache.cassandra.index.IndexNotAvailableException;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.tcm.NotCMSException;
import org.apache.cassandra.utils.vint.VIntCoding;
-import static java.lang.Math.max;
import static org.apache.cassandra.net.MessagingService.VERSION_40;
public enum RequestFailureReason
@@ -36,14 +39,16 @@ public enum RequestFailureReason
TIMEOUT (2),
INCOMPATIBLE_SCHEMA (3),
READ_SIZE (4),
+ // below reason is only logged, but it does not have associated exception
NODE_DOWN (5),
INDEX_NOT_AVAILABLE (6),
+ // below reason does not have an associated exception
READ_TOO_MANY_INDEXES (7),
NOT_CMS (8),
INVALID_ROUTING (9),
COORDINATOR_BEHIND (10),
- ;
-
+ // The following codes have been ported from an external fork, where they
were offset explicitly to avoid conflicts.
+ INDEX_BUILD_IN_PROGRESS (503);
public static final Serializer serializer = new Serializer();
public final int code;
@@ -53,26 +58,32 @@ public enum RequestFailureReason
this.code = code;
}
- private static final RequestFailureReason[] codeToReasonMap;
+ private static final Map<Integer, RequestFailureReason> codeToReasonMap =
new HashMap<>();
+ private static final Map<Class<? extends Throwable>, RequestFailureReason>
exceptionToReasonMap = new HashMap<>();
+ private static final int REASONS_WITHOUT_EXCEPTIONS = 3; // UNKNOWN,
NODE_DOWN, and READ_TOO_MANY_INDEXES
static
{
RequestFailureReason[] reasons = values();
- int max = -1;
- for (RequestFailureReason r : reasons)
- max = max(r.code, max);
-
- RequestFailureReason[] codeMap = new RequestFailureReason[max + 1];
-
for (RequestFailureReason reason : reasons)
{
- if (codeMap[reason.code] != null)
+ if (codeToReasonMap.put(reason.code, reason) != null)
throw new RuntimeException("Two RequestFailureReason-s that
map to the same code: " + reason.code);
- codeMap[reason.code] = reason;
}
- codeToReasonMap = codeMap;
+ exceptionToReasonMap.put(TombstoneOverwhelmingException.class,
READ_TOO_MANY_TOMBSTONES);
+ exceptionToReasonMap.put(WriteTimeoutException.class, TIMEOUT);
+ exceptionToReasonMap.put(IncompatibleSchemaException.class,
INCOMPATIBLE_SCHEMA);
+ exceptionToReasonMap.put(ReadSizeAbortException.class, READ_SIZE);
+ exceptionToReasonMap.put(IndexNotAvailableException.class,
INDEX_NOT_AVAILABLE);
+ exceptionToReasonMap.put(NotCMSException.class, NOT_CMS);
+ exceptionToReasonMap.put(InvalidRoutingException.class,
INVALID_ROUTING);
+ exceptionToReasonMap.put(CoordinatorBehindException.class,
COORDINATOR_BEHIND);
+ exceptionToReasonMap.put(IndexBuildInProgressException.class,
INDEX_BUILD_IN_PROGRESS);
+
+ if (exceptionToReasonMap.size() != reasons.length -
REASONS_WITHOUT_EXCEPTIONS)
+ throw new RuntimeException("A new RequestFailureReasons was
probably added and you may need to update the exceptionToReasonMap");
}
public static RequestFailureReason fromCode(int code)
@@ -81,25 +92,18 @@ public enum RequestFailureReason
throw new IllegalArgumentException("RequestFailureReason code must
be non-negative (got " + code + ')');
// be forgiving and return UNKNOWN if we aren't aware of the code -
for forward compatibility
- return code < codeToReasonMap.length ? codeToReasonMap[code] : UNKNOWN;
+ return codeToReasonMap.getOrDefault(code, UNKNOWN);
}
public static RequestFailureReason forException(Throwable t)
{
- if (t instanceof TombstoneOverwhelmingException)
- return READ_TOO_MANY_TOMBSTONES;
-
- if (t instanceof IncompatibleSchemaException)
- return INCOMPATIBLE_SCHEMA;
-
- if (t instanceof NotCMSException)
- return NOT_CMS;
-
- if (t instanceof InvalidRoutingException)
- return INVALID_ROUTING;
+ RequestFailureReason r = exceptionToReasonMap.get(t.getClass());
+ if (r != null)
+ return r;
- if (t instanceof CoordinatorBehindException)
- return COORDINATOR_BEHIND;
+ for (Map.Entry<Class<? extends Throwable>, RequestFailureReason> entry
: exceptionToReasonMap.entrySet())
+ if (entry.getKey().isInstance(t))
+ return entry.getValue();
return UNKNOWN;
}
diff --git
a/src/java/org/apache/cassandra/index/IndexBuildInProgressException.java
b/src/java/org/apache/cassandra/index/IndexBuildInProgressException.java
new file mode 100644
index 0000000000..1807ff36e0
--- /dev/null
+++ b/src/java/org/apache/cassandra/index/IndexBuildInProgressException.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 org.apache.cassandra.index;
+
+/**
+ * Thrown if a secondary index is not currently available because it is
building.
+ */
+public final class IndexBuildInProgressException extends RuntimeException
+{
+ public static final String INDEX_BUILD_IN_PROGRESS_ERROR = "The secondary
index '%s' is not yet available as it is building";
+
+ /**
+ * Creates a new <code>IndexIsBuildingException</code> for the specified
index.
+ * @param index the index
+ */
+ public IndexBuildInProgressException(Index index)
+ {
+ super(String.format(INDEX_BUILD_IN_PROGRESS_ERROR,
index.getIndexMetadata().name));
+ }
+}
diff --git a/src/java/org/apache/cassandra/index/IndexStatusManager.java
b/src/java/org/apache/cassandra/index/IndexStatusManager.java
index cc98def63e..b11ecd1094 100644
--- a/src/java/org/apache/cassandra/index/IndexStatusManager.java
+++ b/src/java/org/apache/cassandra/index/IndexStatusManager.java
@@ -89,6 +89,7 @@ public class IndexStatusManager
{
// UNKNOWN states are transient/rare; only a few replicas should have
this state at any time. See CASSANDRA-19400
Set<Replica> queryableNonSucceeded = new HashSet<>(4);
+ Map<InetAddressAndPort, Index.Status> indexStatusMap = new HashMap<>();
E queryableEndpoints = liveEndpoints.filter(replica -> {
@@ -97,7 +98,10 @@ public class IndexStatusManager
{
Index.Status status = getIndexStatus(replica.endpoint(),
keyspace.getName(), index.getIndexMetadata().name);
if (!index.isQueryable(status))
+ {
+ indexStatusMap.put(replica.endpoint(), status);
return false;
+ }
if (status != Index.Status.BUILD_SUCCEEDED)
allBuilt = false;
@@ -125,7 +129,13 @@ public class IndexStatusManager
{
Map<InetAddressAndPort, RequestFailureReason> failureReasons =
new HashMap<>();
liveEndpoints.without(queryableEndpoints.endpoints())
- .forEach(replica ->
failureReasons.put(replica.endpoint(),
RequestFailureReason.INDEX_NOT_AVAILABLE));
+ .forEach(replica -> {
+ Index.Status status =
indexStatusMap.get(replica.endpoint());
+ if (status ==
Index.Status.FULL_REBUILD_STARTED)
+ failureReasons.put(replica.endpoint(),
RequestFailureReason.INDEX_BUILD_IN_PROGRESS);
+ else
+ failureReasons.put(replica.endpoint(),
RequestFailureReason.INDEX_NOT_AVAILABLE);
+ });
throw new ReadFailureException(level, filtered, required,
false, failureReasons);
}
diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
index 791293fbb9..5f1c6e3d52 100644
--- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
+++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java
@@ -41,6 +41,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.FutureCallback;
+import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -306,17 +307,29 @@ public class SecondaryIndexManager implements
IndexRegistry, INotificationConsum
/**
* Throws an {@link IndexNotAvailableException} if any of the indexes in
the specified {@link Index.QueryPlan} is
- * not queryable, as it's defined by {@link #isIndexQueryable(Index)}.
+ * not queryable, as it's defined by {@link #isIndexQueryable(Index)}. If
the reason for the index to be not available
+ * is that it's building, it will throw an {@link
IndexBuildInProgressException}.
*
* @param queryPlan a query plan
* @throws IndexNotAvailableException if the query plan has any index that
is not queryable
*/
public void checkQueryability(Index.QueryPlan queryPlan)
{
+ InetAddressAndPort endpoint = FBUtilities.getBroadcastAddressAndPort();
+
for (Index index : queryPlan.getIndexes())
{
+ String indexName = index.getIndexMetadata().name;
+ Index.Status indexStatus =
IndexStatusManager.instance.getIndexStatus(endpoint, keyspace.getName(),
indexName);
+
if (!isIndexQueryable(index))
+ {
+ // isQueryable is always true for non-SAI index
implementations, thus we need to check both not queryable and building
+ if (indexStatus == Index.Status.FULL_REBUILD_STARTED)
+ throw new IndexBuildInProgressException(index);
+
throw new IndexNotAvailableException(index);
+ }
}
}
diff --git a/src/java/org/apache/cassandra/net/InboundSink.java
b/src/java/org/apache/cassandra/net/InboundSink.java
index d077039635..2e8c8413dc 100644
--- a/src/java/org/apache/cassandra/net/InboundSink.java
+++ b/src/java/org/apache/cassandra/net/InboundSink.java
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Predicate;
+import org.apache.cassandra.index.IndexBuildInProgressException;
import org.slf4j.LoggerFactory;
import net.openhft.chronicle.core.util.ThrowingConsumer;
@@ -126,13 +127,24 @@ public class InboundSink implements
InboundMessageHandlers.MessageConsumer
fail(message.header, t);
if (t instanceof NotCMSException || t instanceof
CoordinatorBehindException)
+ {
noSpamLogger.warn(t.getMessage());
- else if (t instanceof TombstoneOverwhelmingException || t
instanceof IndexNotAvailableException || t instanceof InvalidRoutingException)
+ }
+ else if (t instanceof TombstoneOverwhelmingException ||
+ t instanceof IndexNotAvailableException ||
+ t instanceof IndexBuildInProgressException ||
+ t instanceof InvalidRoutingException)
+ {
noSpamLogger.error(t.getMessage());
+ }
else if (t instanceof RuntimeException)
+ {
throw (RuntimeException) t;
+ }
else
+ {
throw new RuntimeException(t);
+ }
}
}
diff --git
a/test/distributed/org/apache/cassandra/distributed/test/sai/IndexAvailabilityTest.java
b/test/distributed/org/apache/cassandra/distributed/test/sai/IndexAvailabilityTest.java
index 53cca2614d..66a63fa11b 100644
---
a/test/distributed/org/apache/cassandra/distributed/test/sai/IndexAvailabilityTest.java
+++
b/test/distributed/org/apache/cassandra/distributed/test/sai/IndexAvailabilityTest.java
@@ -33,6 +33,7 @@ import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.test.TestBaseImpl;
import org.apache.cassandra.index.Index;
@@ -48,6 +49,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
import static
org.apache.cassandra.distributed.test.sai.SAIUtil.waitForIndexQueryable;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals;
@@ -57,7 +59,7 @@ public class IndexAvailabilityTest extends TestBaseImpl
private static final String CREATE_TABLE = "CREATE TABLE %s.%s (pk text
primary key, v1 int, v2 text) " +
"WITH compaction = {'class' :
'SizeTieredCompactionStrategy', 'enabled' : false }";
private static final String CREATE_INDEX = "CREATE CUSTOM INDEX %s ON
%s.%s(%s) USING 'StorageAttachedIndex'";
-
+
private static final Map<NodeIndex, Index.Status>
expectedNodeIndexQueryability = new ConcurrentHashMap<>();
private List<String> keyspaces;
private List<String> indexesPerKs;
@@ -188,6 +190,82 @@ public class IndexAvailabilityTest extends TestBaseImpl
});
}
+ @Test
+ public void testIndexExceptionsTwoIndexesOn3NodeCluster() throws Exception
+ {
+ try (Cluster cluster = init(Cluster.build(3)
+ .withConfig(config -> config.with(GOSSIP)
+ .with(NETWORK))
+ .start()))
+ {
+ String ks2 = "ks2";
+ String cf1 = "cf1";
+ String index1 = "cf1_idx1";
+ String index2 = "cf1_idx2";
+
+ // Create keyspace, table with correct column types
+ cluster.schemaChange(String.format(CREATE_KEYSPACE, ks2, 2));
+ cluster.schemaChange("CREATE TABLE " + ks2 + '.' + cf1 + " (pk int
PRIMARY KEY, v1 int, v2 int)");
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + " WHERE
v1=0 AND v2=0 ALLOW FILTERING");
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + " WHERE
v2=0 ALLOW FILTERING");
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + " WHERE
v1=0 ALLOW FILTERING");
+
+ cluster.schemaChange(String.format(CREATE_INDEX, index1, ks2, cf1,
"v1"));
+ cluster.schemaChange(String.format(CREATE_INDEX, index2, ks2, cf1,
"v2"));
+ cluster.forEach(node ->
expectedNodeIndexQueryability.put(NodeIndex.create(ks2, index1, node),
Index.Status.BUILD_SUCCEEDED));
+ for (IInvokableInstance node : cluster.get(2, 1, 3))
+ for (IInvokableInstance replica : cluster.get(1, 2, 3))
+ waitForIndexingStatus(node, ks2, index1, replica,
Index.Status.BUILD_SUCCEEDED);
+
+ // Mark only index2 as building on node3, leave index1 in
BUILD_SUCCEEDED state
+ markIndexBuilding(cluster.get(3), ks2, cf1, index2);
+ cluster.forEach(node ->
expectedNodeIndexQueryability.put(NodeIndex.create(ks2, index2, node),
Index.Status.FULL_REBUILD_STARTED));
+ for (IInvokableInstance node : cluster.get(1, 2, 3))
+ waitForIndexingStatus(node, ks2, index2, cluster.get(3),
Index.Status.FULL_REBUILD_STARTED);
+
+ assertThatThrownBy(() ->
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + "
WHERE v1=0 AND v2=0"))
+ .hasMessageContaining("Operation failed - received 1
responses and 1 failures: INDEX_BUILD_IN_PROGRESS");
+
+ // Mark only index2 as failing on node2, leave index1 in
BUILD_SUCCEEDED state
+ markIndexBuilding(cluster.get(2), ks2, cf1, index2);
+ cluster.forEach(node ->
expectedNodeIndexQueryability.put(NodeIndex.create(ks2, index2, node),
Index.Status.FULL_REBUILD_STARTED));
+ for (IInvokableInstance node : cluster.get(1, 2, 3))
+ waitForIndexingStatus(node, ks2, index2, cluster.get(2),
Index.Status.FULL_REBUILD_STARTED);
+
+
+ assertThatThrownBy(() ->
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + "
WHERE v1=0 AND v2=0"))
+ .hasMessageContaining("Operation failed - received 1
responses and 1 failures: INDEX_BUILD_IN_PROGRESS");
+
+ // Mark only index2 as failing on node1, leave index1 in
BUILD_SUCCEEDED state
+ markIndexNonQueryable(cluster.get(1), ks2, cf1, index2);
+ cluster.forEach(node ->
expectedNodeIndexQueryability.put(NodeIndex.create(ks2, index2, node),
Index.Status.BUILD_FAILED));
+ for (IInvokableInstance node : cluster.get(1, 2, 3)) {
+ waitForIndexingStatus(node, ks2, index2, cluster.get(1),
Index.Status.BUILD_FAILED);
+ }
+
+ assertThatThrownBy(() ->
+ executeOnAllCoordinators(cluster,
+ "SELECT pk FROM " + ks2 + '.' + cf1 + "
WHERE v1=0 AND v2=0"))
+ .hasMessageMatching("^Operation failed - received 0
responses and 2 failures: INDEX_NOT_AVAILABLE from .+, INDEX_BUILD_IN_PROGRESS
from .+$");
+ }
+ }
+
+ private void executeOnAllCoordinators(Cluster cluster, String query)
+ {
+ // test different coordinator
+ for (int nodeId = 1; nodeId <= cluster.size(); nodeId++)
+ {
+ assertEquals(0, cluster.coordinator(nodeId).execute(query,
ConsistencyLevel.LOCAL_QUORUM).length);
+ }
+ }
+
@SuppressWarnings("DataFlowIssue")
private void markIndexQueryable(IInvokableInstance node, String keyspace,
String table, String indexName)
{
diff --git
a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
index c1365e4cc3..6888ff3a93 100644
---
a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
+++
b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java
@@ -25,12 +25,11 @@ import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import com.google.common.collect.ImmutableSet;
+
import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;
-import org.apache.cassandra.index.internal.CassandraIndex;
-import org.apache.cassandra.index.sai.StorageAttachedIndex;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.config.DatabaseDescriptor;
@@ -46,10 +45,12 @@ import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
-import org.apache.cassandra.index.IndexNotAvailableException;
+import org.apache.cassandra.index.IndexBuildInProgressException;
import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.index.StubIndex;
+import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.index.internal.CustomCassandraIndex;
+import org.apache.cassandra.index.sai.StorageAttachedIndex;
import org.apache.cassandra.index.sasi.SASIIndex;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.service.ClientState;
@@ -1090,7 +1091,7 @@ public class SecondaryIndexTest extends CQLTester
execute("SELECT value FROM %s WHERE value = 2");
fail();
}
- catch (IndexNotAvailableException e)
+ catch (IndexBuildInProgressException e)
{
assertTrue(true);
}
@@ -1124,7 +1125,7 @@ public class SecondaryIndexTest extends CQLTester
indexName = createIndexAsync("CREATE CUSTOM INDEX ON %s (value) USING
'" + ReadOnlyOnFailureIndex.class.getName() + "'");
index = (ReadOnlyOnFailureIndex)
getCurrentColumnFamilyStore().indexManager.getIndexByName(indexName);
waitForIndexBuilds(indexName);
- assertInvalidThrow(IndexNotAvailableException.class, "SELECT value
FROM %s WHERE value = 1");
+ assertInvalidThrow(IndexBuildInProgressException.class, "SELECT value
FROM %s WHERE value = 1");
execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, 1, 1);
assertEquals(0, index.rowsInserted.size());
@@ -1164,7 +1165,7 @@ public class SecondaryIndexTest extends CQLTester
waitForIndexBuilds(indexName);
execute("INSERT INTO %s (pk, ck, value) VALUES (?, ?, ?)", 1, 1, 1);
assertEquals(1, index.rowsInserted.size());
- assertInvalidThrow(IndexNotAvailableException.class, "SELECT value
FROM %s WHERE value = 1");
+ assertInvalidThrow(IndexBuildInProgressException.class, "SELECT value
FROM %s WHERE value = 1");
// Upon recovery, we can query data again
index.reset();
diff --git
a/test/unit/org/apache/cassandra/exceptions/RequestFailureReasonTest.java
b/test/unit/org/apache/cassandra/exceptions/RequestFailureReasonTest.java
index b2fdcd365d..3b89fe9c64 100644
--- a/test/unit/org/apache/cassandra/exceptions/RequestFailureReasonTest.java
+++ b/test/unit/org/apache/cassandra/exceptions/RequestFailureReasonTest.java
@@ -20,8 +20,10 @@ package org.apache.cassandra.exceptions;
import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
+
public class RequestFailureReasonTest
{
private static final RequestFailureReason[] REASONS =
RequestFailureReason.values();
@@ -37,7 +39,8 @@ public class RequestFailureReasonTest
{ 7, "READ_TOO_MANY_INDEXES" },
{ 8, "NOT_CMS" },
{ 9, "INVALID_ROUTING" },
- { 10, "COORDINATOR_BEHIND" }
+ { 10, "COORDINATOR_BEHIND" },
+ { 503, "INDEX_BUILD_IN_PROGRESS" }
};
@Test
@@ -54,4 +57,42 @@ public class RequestFailureReasonTest
assertEquals("Number of RequestFailureReason enum constants has
changed. Update the test.",
EXPECTED_VALUES.length, REASONS.length);
}
+
+ @Test
+ public void testFromCode()
+ {
+ // Test valid codes
+ for (Object[] expected : EXPECTED_VALUES)
+ {
+ int code = (Integer) expected[0];
+ String name = (String) expected[1];
+ assertEquals(RequestFailureReason.valueOf(name),
RequestFailureReason.fromCode(code));
+ }
+
+ // Test invalid codes
+ assertEquals(RequestFailureReason.UNKNOWN,
RequestFailureReason.fromCode(200));
+ assertEquals(RequestFailureReason.UNKNOWN,
RequestFailureReason.fromCode(999));
+ assertThatThrownBy(() ->
RequestFailureReason.fromCode(-1)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testExceptionSubclassMapping()
+ {
+ // Create a subclass of UnknownTableException
+ class CustomUnknownTableException extends IncompatibleSchemaException
+ {
+ public CustomUnknownTableException(String ks)
+ {
+ super(ks);
+ }
+ }
+
+ // Verify the parent class still maps correctly
+ assertEquals(RequestFailureReason.INCOMPATIBLE_SCHEMA,
+ RequestFailureReason.forException(new
CustomUnknownTableException("ks")));
+
+ // Test unmapped exception returns UNKNOWN
+ assertEquals(RequestFailureReason.UNKNOWN,
+ RequestFailureReason.forException(new
RuntimeException("test")));
+ }
}
diff --git a/test/unit/org/apache/cassandra/index/IndexStatusManagerTest.java
b/test/unit/org/apache/cassandra/index/IndexStatusManagerTest.java
index 947b7a57bc..39401ac1bc 100644
--- a/test/unit/org/apache/cassandra/index/IndexStatusManagerTest.java
+++ b/test/unit/org/apache/cassandra/index/IndexStatusManagerTest.java
@@ -359,7 +359,7 @@ public class IndexStatusManagerTest
.hasMessageStartingWith("Operation failed")
.hasMessageContaining("INDEX_NOT_AVAILABLE from
/127.0.0.253:7000")
.hasMessageContaining("INDEX_NOT_AVAILABLE from
/127.0.0.254:7000")
- .hasMessageContaining("INDEX_NOT_AVAILABLE from
/127.0.0.255:7000");
+ .hasMessageContaining("INDEX_BUILD_IN_PROGRESS from
/127.0.0.255:7000");
}
void runTest(Testcase testcase)
diff --git
a/test/unit/org/apache/cassandra/index/sai/cql/AllowFilteringTest.java
b/test/unit/org/apache/cassandra/index/sai/cql/AllowFilteringTest.java
index 7a9198a700..3ae0905185 100644
--- a/test/unit/org/apache/cassandra/index/sai/cql/AllowFilteringTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/cql/AllowFilteringTest.java
@@ -21,10 +21,14 @@ package org.apache.cassandra.index.sai.cql;
import org.junit.Test;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
+import org.apache.cassandra.index.IndexBuildInProgressException;
import org.apache.cassandra.index.sai.SAITester;
import org.apache.cassandra.index.sai.StorageAttachedIndex;
+import org.apache.cassandra.inject.Injections;
+import org.apache.cassandra.inject.InvokePointBuilder;
import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertNotNull;
/**
@@ -391,4 +395,32 @@ public class AllowFilteringTest extends SAITester
assertNotNull(execute(query + " ALLOW FILTERING"));
}
+ private static final Injections.Barrier blockIndexBuild =
Injections.newBarrier("block_index_build", 2, false)
+
.add(InvokePointBuilder.newInvokePoint()
+
.onClass(StorageAttachedIndex.class)
+
.onMethod("startInitialBuild"))
+
.build();
+
+ @Test
+ public void testAllowFilteringDuringIndexBuild() throws Throwable
+ {
+ createTable("CREATE TABLE %s (k int PRIMARY KEY, v int)");
+ Injections.inject(blockIndexBuild);
+ String idx = createIndexAsync(String.format("CREATE CUSTOM INDEX ON
%%s(v) USING '%s'", StorageAttachedIndex.class.getName()));
+
+ String expectedErrorMessage =
String.format(IndexBuildInProgressException.INDEX_BUILD_IN_PROGRESS_ERROR, idx);
+ assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE v=0"))
+ .hasMessage(expectedErrorMessage)
+ .isInstanceOf(IndexBuildInProgressException.class);
+
+ assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE v=0 ALLOW
FILTERING"))
+ .hasMessage(expectedErrorMessage)
+ .isInstanceOf(IndexBuildInProgressException.class);
+
+ blockIndexBuild.countDown();
+ blockIndexBuild.disable();
+ waitForIndexQueryable(idx);
+ execute("SELECT * FROM %s WHERE v=0");
+ execute("SELECT * FROM %s WHERE v=0 ALLOW FILTERING");
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]