mirror of https://github.com/apache/cassandra
870 lines
34 KiB
Java
870 lines
34 KiB
Java
/*
|
|
* 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;
|
|
|
|
import java.io.FileNotFoundException;
|
|
import java.net.SocketException;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import com.google.common.collect.Sets;
|
|
|
|
import org.junit.After;
|
|
import org.junit.Test;
|
|
|
|
import org.apache.cassandra.Util;
|
|
import org.apache.cassandra.cql3.CQLTester;
|
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
|
import org.apache.cassandra.db.SystemKeyspace;
|
|
import org.apache.cassandra.db.compaction.CompactionInfo;
|
|
import org.apache.cassandra.db.lifecycle.SSTableSet;
|
|
import org.apache.cassandra.exceptions.ConfigurationException;
|
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
|
import org.apache.cassandra.notifications.SSTableAddedNotification;
|
|
import org.apache.cassandra.schema.IndexMetadata;
|
|
import org.apache.cassandra.utils.ClassLoadingTestNonAssignable;
|
|
import org.apache.cassandra.utils.ClassLoadingTestSupport;
|
|
import org.apache.cassandra.utils.JVMStabilityInspector;
|
|
import org.apache.cassandra.utils.KillerForTests;
|
|
import org.apache.cassandra.utils.concurrent.Refs;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertTrue;
|
|
import static org.junit.Assert.fail;
|
|
|
|
public class SecondaryIndexManagerTest extends CQLTester
|
|
{
|
|
@After
|
|
public void after()
|
|
{
|
|
TestingIndex.clear();
|
|
}
|
|
|
|
@Test
|
|
public void rejectsNonIndexClassWithoutInitializing()
|
|
{
|
|
ClassLoadingTestSupport.assertNotInitialized(ClassLoadingTestNonAssignable.class);
|
|
try
|
|
{
|
|
SecondaryIndexManager.loadIndexClass(ClassLoadingTestNonAssignable.class.getName());
|
|
fail("Should not pass");
|
|
}
|
|
catch (ConfigurationException e)
|
|
{
|
|
assertTrue(e.getMessage().contains("must extend or implement " + Index.class.getName()));
|
|
}
|
|
|
|
assertFalse(ClassLoadingTestSupport.wasInitialized(ClassLoadingTestNonAssignable.class));
|
|
}
|
|
|
|
@Test
|
|
public void createSasiAfterSai()
|
|
{
|
|
createTable("CREATE TABLE %s (id int PRIMARY KEY, val text)");
|
|
createIndex("CREATE INDEX idx0 ON %s (val) USING 'sai'");
|
|
execute("INSERT INTO %s (id, val) VALUES (1, 'a')");
|
|
execute("SELECT * FROM %s WHERE val = 'a'");
|
|
flush();
|
|
createIndex("CREATE CUSTOM INDEX idx1 ON %s (val) USING 'org.apache.cassandra.index.sasi.SASIIndex'");
|
|
execute("SELECT * FROM %s WHERE val = 'a'");
|
|
}
|
|
|
|
@Test
|
|
public void creatingIndexMarksTheIndexAsBuilt()
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndex("CREATE INDEX ON %s(c)");
|
|
|
|
assertMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
@Test
|
|
public void rebuilOrRecoveringIndexMarksTheIndexAsBuilt() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndex("CREATE INDEX ON %s(c)");
|
|
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
assertTrue(tryRebuild(indexName, false));
|
|
assertMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
@Test
|
|
public void recreatingIndexMarksTheIndexAsBuilt() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndex("CREATE INDEX ON %s(c)");
|
|
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
// drop the index and verify that it has been removed from the built indexes table
|
|
dropIndex("DROP INDEX %s." + indexName);
|
|
assertNotMarkedAsBuilt(indexName);
|
|
|
|
// create the index again and verify that it's added to the built indexes table
|
|
createIndex(String.format("CREATE INDEX %s ON %%s(c)", indexName));
|
|
assertMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
@Test
|
|
public void addingSSTablesMarksTheIndexAsBuilt()
|
|
{
|
|
Util.assumeLegacySecondaryIndex();
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndex("CREATE INDEX ON %s(c)");
|
|
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
cfs.indexManager.markAllIndexesRemoved();
|
|
assertNotMarkedAsBuilt(indexName);
|
|
|
|
try (Refs<SSTableReader> sstables = Refs.ref(cfs.getSSTables(SSTableSet.CANONICAL)))
|
|
{
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(sstables, null), cfs.getTracker());
|
|
assertMarkedAsBuilt(indexName);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void cannotRebuildRecoverWhileInitializationIsInProgress() throws Throwable
|
|
{
|
|
// create an index which blocks on creation
|
|
TestingIndex.blockCreate();
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(b) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(b) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
|
|
// try to rebuild/recover the index before the index creation task has finished
|
|
assertFalse(tryRebuild(defaultIndexName, false));
|
|
assertFalse(tryRebuild(readOnlyIndexName, false));
|
|
assertFalse(tryRebuild(writeOnlyIndexName, false));
|
|
assertNotMarkedAsBuilt(defaultIndexName);
|
|
assertNotMarkedAsBuilt(readOnlyIndexName);
|
|
assertNotMarkedAsBuilt(writeOnlyIndexName);
|
|
|
|
// check that the index is marked as built when the creation finishes
|
|
TestingIndex.unblockCreate();
|
|
waitForIndexQueryable(defaultIndexName);
|
|
waitForIndexQueryable(readOnlyIndexName);
|
|
waitForIndexQueryable(writeOnlyIndexName);
|
|
assertMarkedAsBuilt(defaultIndexName);
|
|
assertMarkedAsBuilt(readOnlyIndexName);
|
|
assertMarkedAsBuilt(writeOnlyIndexName);
|
|
|
|
// now verify you can rebuild/recover
|
|
assertTrue(tryRebuild(defaultIndexName, false));
|
|
assertTrue(tryRebuild(readOnlyIndexName, false));
|
|
assertTrue(tryRebuild(readOnlyIndexName, false));
|
|
assertMarkedAsBuilt(defaultIndexName);
|
|
assertMarkedAsBuilt(readOnlyIndexName);
|
|
assertMarkedAsBuilt(writeOnlyIndexName);
|
|
}
|
|
|
|
@Test
|
|
public void cannotRebuildOrRecoverWhileAnotherRebuildIsInProgress() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(b) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(b) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
final AtomicBoolean error = new AtomicBoolean();
|
|
|
|
// verify it's built after initialization:
|
|
assertMarkedAsBuilt(defaultIndexName);
|
|
assertMarkedAsBuilt(readOnlyIndexName);
|
|
assertMarkedAsBuilt(writeOnlyIndexName);
|
|
|
|
// rebuild the index in another thread, but make it block:
|
|
TestingIndex.blockBuild();
|
|
Thread asyncBuild = new Thread(() -> {
|
|
try
|
|
{
|
|
tryRebuild(defaultIndexName, false);
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
error.set(true);
|
|
}
|
|
});
|
|
asyncBuild.start();
|
|
|
|
// wait for the rebuild to block, so that we can proceed unblocking all further operations:
|
|
TestingIndex.waitBlockedOnBuild();
|
|
|
|
// do not block further builds:
|
|
TestingIndex.shouldBlockBuild = false;
|
|
|
|
// verify rebuilding the index before the previous index build task has finished fails
|
|
assertFalse(tryRebuild(defaultIndexName, false));
|
|
assertNotMarkedAsBuilt(defaultIndexName);
|
|
|
|
// check that the index is marked as built when the build finishes
|
|
TestingIndex.unblockBuild();
|
|
asyncBuild.join();
|
|
assertMarkedAsBuilt(defaultIndexName);
|
|
assertMarkedAsBuilt(readOnlyIndexName);
|
|
assertMarkedAsBuilt(writeOnlyIndexName);
|
|
|
|
// now verify you can rebuild
|
|
assertTrue(tryRebuild(defaultIndexName, false));
|
|
assertTrue(tryRebuild(readOnlyIndexName, false));
|
|
assertTrue(tryRebuild(writeOnlyIndexName, false));
|
|
assertMarkedAsBuilt(defaultIndexName);
|
|
assertMarkedAsBuilt(readOnlyIndexName);
|
|
assertMarkedAsBuilt(writeOnlyIndexName);
|
|
}
|
|
|
|
@Test
|
|
public void cannotRebuildWhileAnSSTableBuildIsInProgress() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
final String indexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
final AtomicBoolean error = new AtomicBoolean();
|
|
|
|
// verify it's built after initialization:
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
// add sstables in another thread, but make it block:
|
|
TestingIndex.blockBuild();
|
|
Thread asyncBuild = new Thread(() -> {
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
try (Refs<SSTableReader> sstables = Refs.ref(cfs.getSSTables(SSTableSet.CANONICAL)))
|
|
{
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(sstables, null), cfs.getTracker());
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
error.set(true);
|
|
}
|
|
});
|
|
asyncBuild.start();
|
|
|
|
// wait for the build to block, so that we can proceed unblocking all further operations:
|
|
TestingIndex.waitBlockedOnBuild();
|
|
|
|
// do not block further builds:
|
|
TestingIndex.shouldBlockBuild = false;
|
|
|
|
// verify rebuilding the index before the previous index build task has finished fails
|
|
assertFalse(tryRebuild(indexName, false));
|
|
assertNotMarkedAsBuilt(indexName);
|
|
|
|
// check that the index is marked as built when the build finishes
|
|
TestingIndex.unblockBuild();
|
|
asyncBuild.join();
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
// now verify you can rebuild
|
|
assertTrue(tryRebuild(indexName, false));
|
|
assertMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
@Test
|
|
public void addingSSTableWhileRebuildIsInProgress() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
final String indexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
final AtomicBoolean error = new AtomicBoolean();
|
|
|
|
// verify it's built after initialization:
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
// rebuild the index in another thread, but make it block:
|
|
TestingIndex.blockBuild();
|
|
Thread asyncBuild = new Thread(() -> {
|
|
try
|
|
{
|
|
tryRebuild(indexName, false);
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
error.set(true);
|
|
}
|
|
});
|
|
asyncBuild.start();
|
|
|
|
// wait for the rebuild to block, so that we can proceed unblocking all further operations:
|
|
TestingIndex.waitBlockedOnBuild();
|
|
|
|
// do not block further builds:
|
|
TestingIndex.shouldBlockBuild = false;
|
|
|
|
// try adding sstables and verify they are built but the index is not marked as built because of the pending build:
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
try (Refs<SSTableReader> sstables = Refs.ref(cfs.getSSTables(SSTableSet.CANONICAL)))
|
|
{
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(sstables, null), cfs.getTracker());
|
|
assertNotMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
// unblock the pending build:
|
|
TestingIndex.unblockBuild();
|
|
asyncBuild.join();
|
|
|
|
// verify the index is now built:
|
|
assertMarkedAsBuilt(indexName);
|
|
assertFalse(error.get());
|
|
}
|
|
|
|
@Test
|
|
public void addingSSTableWithBuildFailureWhileRebuildIsInProgress() throws Throwable
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
final String indexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
final AtomicBoolean error = new AtomicBoolean();
|
|
|
|
// verify it's built after initialization:
|
|
assertMarkedAsBuilt(indexName);
|
|
|
|
// rebuild the index in another thread, but make it block:
|
|
TestingIndex.blockBuild();
|
|
Thread asyncBuild = new Thread(() -> {
|
|
try
|
|
{
|
|
tryRebuild(indexName, false);
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
error.set(true);
|
|
}
|
|
});
|
|
asyncBuild.start();
|
|
|
|
// wait for the rebuild to block, so that we can proceed unblocking all further operations:
|
|
TestingIndex.waitBlockedOnBuild();
|
|
|
|
// do not block further builds:
|
|
TestingIndex.shouldBlockBuild = false;
|
|
|
|
// try adding sstables but make the build fail:
|
|
TestingIndex.shouldFailBuild = true;
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
try (Refs<SSTableReader> sstables = Refs.ref(cfs.getSSTables(SSTableSet.CANONICAL)))
|
|
{
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(sstables, null), cfs.getTracker());
|
|
fail("Should have failed!");
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
assertTrue(ex.getMessage().contains("configured to fail"));
|
|
}
|
|
|
|
// disable failures:
|
|
TestingIndex.shouldFailBuild = false;
|
|
|
|
// unblock the pending build:
|
|
TestingIndex.unblockBuild();
|
|
asyncBuild.join();
|
|
|
|
// verify the index is *not* built due to the failing sstable build:
|
|
assertNotMarkedAsBuilt(indexName);
|
|
assertFalse(error.get());
|
|
}
|
|
|
|
@Test
|
|
public void rebuildWithFailure()
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
final String indexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
|
|
// Rebuild the index with failure and verify it is not marked as built
|
|
TestingIndex.shouldFailBuild = true;
|
|
try
|
|
{
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
cfs.indexManager.rebuildIndexesBlocking(Collections.singleton(indexName));
|
|
fail("Should have failed!");
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
assertTrue(ex.getMessage().contains("configured to fail"));
|
|
}
|
|
assertNotMarkedAsBuilt(indexName);
|
|
}
|
|
|
|
@Test
|
|
public void initializingIndexNotQueryableButMaybeWritable()
|
|
{
|
|
TestingIndex.blockCreate();
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
|
|
// the index shouldn't be queryable while the initialization hasn't finished
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertTrue(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
|
|
// the index should be queryable once the initialization has finished
|
|
TestingIndex.unblockCreate();
|
|
waitForIndexQueryable(defaultIndexName);
|
|
waitForIndexQueryable(readOnlyIndexName);
|
|
waitForIndexQueryable(writeOnlyIndexName);
|
|
assertTrue(isQueryable(defaultIndexName));
|
|
assertTrue(isQueryable(readOnlyIndexName));
|
|
assertTrue(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertTrue(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
}
|
|
|
|
@Test
|
|
public void initializingIndexNotQueryableButMaybeNotWritableAfterPartialRebuild()
|
|
{
|
|
TestingIndex.blockCreate();
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
|
|
// the index should never be queryable while the initialization hasn't finished
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
|
|
// the index should always we writable while the initialization hasn't finished
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertTrue(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
|
|
// a failing partial build doesn't set the index as queryable, but might set it as not writable
|
|
TestingIndex.shouldFailBuild = true;
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
try
|
|
{
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(cfs.getLiveSSTables(), null), this);
|
|
fail("Should have failed!");
|
|
}
|
|
catch (Throwable ex)
|
|
{
|
|
assertTrue(ex.getMessage().contains("configured to fail"));
|
|
}
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertFalse(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
|
|
// a successful partial build doesn't set the index as queryable nor writable
|
|
TestingIndex.shouldFailBuild = false;
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(cfs.getLiveSSTables(), null), this);
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertFalse(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
|
|
// the index should be queryable once the initialization has finished
|
|
TestingIndex.unblockCreate();
|
|
waitForIndexQueryable(defaultIndexName);
|
|
assertTrue(isQueryable(defaultIndexName));
|
|
assertTrue(isQueryable(readOnlyIndexName));
|
|
assertTrue(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertTrue(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
}
|
|
|
|
@Test
|
|
public void indexWithFailedInitializationIsQueryableAndWritableAfterFullRebuild() throws Throwable
|
|
{
|
|
TestingIndex.shouldFailCreate = true;
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
waitForIndexBuilds(defaultIndexName);
|
|
waitForIndexBuilds(readOnlyIndexName);
|
|
waitForIndexBuilds(writeOnlyIndexName);
|
|
|
|
tryRebuild(defaultIndexName, true);
|
|
tryRebuild(readOnlyIndexName, true);
|
|
tryRebuild(writeOnlyIndexName, true);
|
|
TestingIndex.shouldFailCreate = false;
|
|
|
|
// a successfull full rebuild should set the index as queryable/writable
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
cfs.indexManager.rebuildIndexesBlocking(Sets.newHashSet(defaultIndexName, readOnlyIndexName, writeOnlyIndexName));
|
|
assertTrue(isQueryable(defaultIndexName));
|
|
assertTrue(isQueryable(readOnlyIndexName));
|
|
assertTrue(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertTrue(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
}
|
|
|
|
@Test
|
|
public void indexWithFailedInitializationDoesNotChangeQueryabilityNorWritabilityAfterPartialRebuild()
|
|
{
|
|
TestingIndex.shouldFailCreate = true;
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String defaultIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
String readOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", ReadOnlyOnFailureIndex.class.getName()));
|
|
String writeOnlyIndexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", WriteOnlyOnFailureIndex.class.getName()));
|
|
waitForIndexBuilds(defaultIndexName);
|
|
waitForIndexBuilds(readOnlyIndexName);
|
|
waitForIndexBuilds(writeOnlyIndexName);
|
|
TestingIndex.shouldFailCreate = false;
|
|
|
|
// the index should never be queryable, but it could be writable after the failed initialization
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertFalse(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
|
|
// a successful partial build doesn't set the index as queryable nor writable
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
cfs.indexManager.handleNotification(new SSTableAddedNotification(cfs.getLiveSSTables(), null), this);
|
|
waitForIndexBuilds(defaultIndexName);
|
|
waitForIndexBuilds(readOnlyIndexName);
|
|
waitForIndexBuilds(writeOnlyIndexName);
|
|
assertFalse(isQueryable(defaultIndexName));
|
|
assertFalse(isQueryable(readOnlyIndexName));
|
|
assertFalse(isQueryable(writeOnlyIndexName));
|
|
assertTrue(isWritable(defaultIndexName));
|
|
assertFalse(isWritable(readOnlyIndexName));
|
|
assertTrue(isWritable(writeOnlyIndexName));
|
|
}
|
|
|
|
@Test
|
|
public void handleJVMStablityOnFailedCreate()
|
|
{
|
|
handleJVMStablityOnFailedCreate(new SocketException("Should not fail"), false);
|
|
handleJVMStablityOnFailedCreate(new FileNotFoundException("Should not fail"), false);
|
|
handleJVMStablityOnFailedCreate(new SocketException("Too many open files"), true);
|
|
handleJVMStablityOnFailedCreate(new FileNotFoundException("Too many open files"), true);
|
|
handleJVMStablityOnFailedCreate(new RuntimeException("Should not fail"), false);
|
|
}
|
|
|
|
private void handleJVMStablityOnFailedCreate(Throwable throwable, boolean shouldKillJVM)
|
|
{
|
|
KillerForTests killerForTests = new KillerForTests();
|
|
JVMStabilityInspector.Killer originalKiller = JVMStabilityInspector.replaceKiller(killerForTests);
|
|
|
|
try
|
|
{
|
|
TestingIndex.shouldFailCreate = true;
|
|
TestingIndex.failedCreateThrowable = throwable;
|
|
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndexAsync(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
tryRebuild(indexName, true);
|
|
fail("Should have failed!");
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
assertEquals(shouldKillJVM, killerForTests.wasKilled());
|
|
}
|
|
finally
|
|
{
|
|
JVMStabilityInspector.replaceKiller(originalKiller);
|
|
TestingIndex.shouldFailCreate = false;
|
|
TestingIndex.failedCreateThrowable = null;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void handleJVMStablityOnFailedRebuild()
|
|
{
|
|
handleJVMStablityOnFailedRebuild(new SocketException("Should not fail"), false);
|
|
handleJVMStablityOnFailedRebuild(new FileNotFoundException("Should not fail"), false);
|
|
handleJVMStablityOnFailedRebuild(new SocketException("Too many open files"), true);
|
|
handleJVMStablityOnFailedRebuild(new FileNotFoundException("Too many open files"), true);
|
|
handleJVMStablityOnFailedRebuild(new RuntimeException("Should not fail"), false);
|
|
}
|
|
|
|
private void handleJVMStablityOnFailedRebuild(Throwable throwable, boolean shouldKillJVM)
|
|
{
|
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
|
String indexName = createIndex(String.format("CREATE CUSTOM INDEX ON %%s(c) USING '%s'", TestingIndex.class.getName()));
|
|
|
|
KillerForTests killerForTests = new KillerForTests();
|
|
JVMStabilityInspector.Killer originalKiller = JVMStabilityInspector.replaceKiller(killerForTests);
|
|
|
|
try
|
|
{
|
|
TestingIndex.shouldFailBuild = true;
|
|
TestingIndex.failedBuildTrowable = throwable;
|
|
|
|
getCurrentColumnFamilyStore().indexManager.rebuildIndexesBlocking(Collections.singleton(indexName));
|
|
fail("Should have failed!");
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
assertEquals(shouldKillJVM, killerForTests.wasKilled());
|
|
}
|
|
finally
|
|
{
|
|
JVMStabilityInspector.replaceKiller(originalKiller);
|
|
TestingIndex.shouldFailBuild = false;
|
|
TestingIndex.failedBuildTrowable = null;
|
|
}
|
|
}
|
|
|
|
private static void assertMarkedAsBuilt(String indexName)
|
|
{
|
|
List<String> indexes = SystemKeyspace.getBuiltIndexes(KEYSPACE, Collections.singleton(indexName));
|
|
assertEquals(1, indexes.size());
|
|
assertEquals(indexName, indexes.get(0));
|
|
}
|
|
|
|
private static void assertNotMarkedAsBuilt(String indexName)
|
|
{
|
|
List<String> indexes = SystemKeyspace.getBuiltIndexes(KEYSPACE, Collections.singleton(indexName));
|
|
assertTrue(indexes.isEmpty());
|
|
}
|
|
|
|
private boolean tryRebuild(String indexName, boolean wait) throws Throwable
|
|
{
|
|
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
|
|
boolean done = false;
|
|
do
|
|
{
|
|
try
|
|
{
|
|
cfs.indexManager.rebuildIndexesBlocking(Collections.singleton(indexName));
|
|
done = true;
|
|
}
|
|
catch (IllegalStateException e)
|
|
{
|
|
assertTrue(e.getMessage().contains("currently in progress"));
|
|
}
|
|
Thread.sleep(500);
|
|
}
|
|
while (!done && wait);
|
|
|
|
return done;
|
|
}
|
|
|
|
private boolean isQueryable(String indexName)
|
|
{
|
|
SecondaryIndexManager manager = getCurrentColumnFamilyStore().indexManager;
|
|
Index index = manager.getIndexByName(indexName);
|
|
return manager.isIndexQueryable(index);
|
|
}
|
|
|
|
private boolean isWritable(String indexName)
|
|
{
|
|
SecondaryIndexManager manager = getCurrentColumnFamilyStore().indexManager;
|
|
Index index = manager.getIndexByName(indexName);
|
|
return manager.isIndexWritable(index);
|
|
}
|
|
|
|
public static class TestingIndex extends StubIndex
|
|
{
|
|
private static volatile CountDownLatch createLatch;
|
|
private static volatile CountDownLatch buildLatch;
|
|
private static volatile CountDownLatch createWaitLatch;
|
|
private static volatile CountDownLatch buildWaitLatch;
|
|
static volatile boolean shouldBlockCreate = false;
|
|
static volatile boolean shouldBlockBuild = false;
|
|
static volatile boolean shouldFailCreate = false;
|
|
static volatile boolean shouldFailBuild = false;
|
|
static volatile Throwable failedCreateThrowable;
|
|
static volatile Throwable failedBuildTrowable;
|
|
|
|
@SuppressWarnings("WeakerAccess")
|
|
public TestingIndex(ColumnFamilyStore baseCfs, IndexMetadata metadata)
|
|
{
|
|
super(baseCfs, metadata);
|
|
}
|
|
|
|
static void blockCreate()
|
|
{
|
|
shouldBlockCreate = true;
|
|
createLatch = new CountDownLatch(1);
|
|
createWaitLatch = new CountDownLatch(1);
|
|
}
|
|
|
|
static void blockBuild()
|
|
{
|
|
shouldBlockBuild = true;
|
|
buildLatch = new CountDownLatch(1);
|
|
buildWaitLatch = new CountDownLatch(1);
|
|
}
|
|
|
|
static void unblockCreate()
|
|
{
|
|
createLatch.countDown();
|
|
}
|
|
|
|
static void unblockBuild()
|
|
{
|
|
buildLatch.countDown();
|
|
}
|
|
|
|
static void waitBlockedOnCreate() throws InterruptedException
|
|
{
|
|
createWaitLatch.await();
|
|
}
|
|
|
|
static void waitBlockedOnBuild() throws InterruptedException
|
|
{
|
|
buildWaitLatch.await();
|
|
}
|
|
|
|
static void clear()
|
|
{
|
|
reset(createLatch);
|
|
reset(createWaitLatch);
|
|
reset(buildLatch);
|
|
reset(buildWaitLatch);
|
|
createLatch = null;
|
|
createWaitLatch = null;
|
|
buildLatch = null;
|
|
buildWaitLatch = null;
|
|
shouldBlockCreate = false;
|
|
shouldBlockBuild = false;
|
|
shouldFailCreate = false;
|
|
shouldFailBuild = false;
|
|
failedCreateThrowable = null;
|
|
failedBuildTrowable = null;
|
|
}
|
|
|
|
private static void reset(CountDownLatch latch)
|
|
{
|
|
if (latch == null)
|
|
return;
|
|
|
|
while (0L < latch.getCount())
|
|
latch.countDown();
|
|
}
|
|
|
|
public Callable<?> getInitializationTask()
|
|
{
|
|
return () ->
|
|
{
|
|
if (shouldBlockCreate && createLatch != null)
|
|
{
|
|
createWaitLatch.countDown();
|
|
createLatch.await();
|
|
}
|
|
|
|
if (shouldFailCreate)
|
|
{
|
|
throw failedCreateThrowable == null
|
|
? new IllegalStateException("Index is configured to fail.")
|
|
: new RuntimeException(failedCreateThrowable);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
}
|
|
|
|
public IndexBuildingSupport getBuildTaskSupport()
|
|
{
|
|
return new CollatedViewIndexBuildingSupport()
|
|
{
|
|
public SecondaryIndexBuilder getIndexBuildTask(ColumnFamilyStore cfs, Set<Index> indexes, Collection<SSTableReader> sstables, boolean isFullRebuild)
|
|
{
|
|
try
|
|
{
|
|
if (shouldBlockBuild && buildLatch != null)
|
|
{
|
|
buildWaitLatch.countDown();
|
|
buildLatch.await();
|
|
}
|
|
final SecondaryIndexBuilder builder = super.getIndexBuildTask(cfs, indexes, sstables, isFullRebuild);
|
|
return new SecondaryIndexBuilder()
|
|
{
|
|
|
|
@Override
|
|
public void build()
|
|
{
|
|
if (shouldFailBuild)
|
|
{
|
|
throw failedBuildTrowable == null
|
|
? new IllegalStateException("Index is configured to fail.")
|
|
: new RuntimeException(failedBuildTrowable);
|
|
}
|
|
builder.build();
|
|
}
|
|
|
|
@Override
|
|
public CompactionInfo getCompactionInfo()
|
|
{
|
|
return builder.getCompactionInfo();
|
|
}
|
|
};
|
|
}
|
|
catch (InterruptedException ex)
|
|
{
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
public boolean shouldBuildBlocking()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <code>TestingIndex</code> that only supports reads when initial build or full rebuild has failed.
|
|
*/
|
|
public static class ReadOnlyOnFailureIndex extends TestingIndex
|
|
{
|
|
public ReadOnlyOnFailureIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
|
{
|
|
super(baseCfs, indexDef);
|
|
}
|
|
|
|
@Override
|
|
public LoadType getSupportedLoadTypeOnFailure(boolean isInitialBuild)
|
|
{
|
|
return LoadType.READ;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <code>TestingIndex</code> that only supports writes when initial build or full rebuild has failed.
|
|
*/
|
|
public static class WriteOnlyOnFailureIndex extends TestingIndex
|
|
{
|
|
public WriteOnlyOnFailureIndex(ColumnFamilyStore baseCfs, IndexMetadata indexDef)
|
|
{
|
|
super(baseCfs, indexDef);
|
|
}
|
|
|
|
@Override
|
|
public LoadType getSupportedLoadTypeOnFailure(boolean isInitialBuild)
|
|
{
|
|
return LoadType.WRITE;
|
|
}
|
|
}
|
|
}
|