/*
* 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.db.compaction;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import com.google.common.collect.Iterables;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
import org.apache.cassandra.notifications.SSTableRepairStatusChanged;
import org.apache.cassandra.repair.consistent.LocalSessionAccessor;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.utils.FBUtilities;
/**
* Tests CompactionStrategyManager's handling of pending repair sstables
*/
public class CompactionStrategyManagerPendingRepairTest extends AbstractPendingRepairTest
{
private static boolean strategiesContain(Collection<AbstractCompactionStrategy> strategies, SSTableReader sstable)
{
return Iterables.any(strategies, strategy -> strategy.getSSTables().contains(sstable));
}
private boolean pendingContains(UUID id, SSTableReader sstable)
{
return Iterables.any(csm.getPendingRepairManagers(), p -> p.get(id) != null && p.get(id).getSSTables().contains(sstable));
}
private boolean pendingContains(SSTableReader sstable)
{
return Iterables.any(csm.getPendingRepairManagers(), p -> strategiesContain(p.getStrategies(), sstable));
}
private boolean repairedContains(SSTableReader sstable)
{
return strategiesContain(csm.getRepaired(), sstable);
}
private boolean unrepairedContains(SSTableReader sstable)
{
return strategiesContain(csm.getUnrepaired(), sstable);
}
/**
* Pending repair strategy should be created when we encounter a new pending id
*/
@Test
public void sstableAdded()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
Assert.assertTrue(csm.pendingRepairs().isEmpty());
SSTableReader sstable = makeSSTable(true);
Assert.assertFalse(sstable.isRepaired());
Assert.assertFalse(sstable.isPendingRepair());
mutateRepaired(sstable, repairID);
Assert.assertFalse(sstable.isRepaired());
Assert.assertTrue(sstable.isPendingRepair());
csm.getForPendingRepair(repairID).forEach(Assert::assertNull);
// add the sstable
csm.handleNotification(new SSTableAddedNotification(Collections.singleton(sstable)), cfs.getTracker());
Assert.assertFalse(repairedContains(sstable));
Assert.assertFalse(unrepairedContains(sstable));
csm.getForPendingRepair(repairID).forEach(Assert::assertNotNull);
Assert.assertTrue(pendingContains(repairID, sstable));
}
@Test
public void sstableListChangedAddAndRemove()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
SSTableReader sstable1 = makeSSTable(true);
mutateRepaired(sstable1, repairID);
SSTableReader sstable2 = makeSSTable(true);
mutateRepaired(sstable2, repairID);
Assert.assertFalse(repairedContains(sstable1));
Assert.assertFalse(unrepairedContains(sstable1));
Assert.assertFalse(repairedContains(sstable2));
Assert.assertFalse(unrepairedContains(sstable2));
csm.getForPendingRepair(repairID).forEach(Assert::assertNull);
// add only
SSTableListChangedNotification notification;
notification = new SSTableListChangedNotification(Collections.singleton(sstable1),
Collections.emptyList(),
OperationType.COMPACTION);
csm.handleNotification(notification, cfs.getTracker());
csm.getForPendingRepair(repairID).forEach(Assert::assertNotNull);
Assert.assertFalse(repairedContains(sstable1));
Assert.assertFalse(unrepairedContains(sstable1));
Assert.assertTrue(pendingContains(repairID, sstable1));
Assert.assertFalse(repairedContains(sstable2));
Assert.assertFalse(unrepairedContains(sstable2));
Assert.assertFalse(pendingContains(repairID, sstable2));
// remove and add
notification = new SSTableListChangedNotification(Collections.singleton(sstable2),
Collections.singleton(sstable1),
OperationType.COMPACTION);
csm.handleNotification(notification, cfs.getTracker());
Assert.assertFalse(repairedContains(sstable1));
Assert.assertFalse(unrepairedContains(sstable1));
Assert.assertFalse(pendingContains(repairID, sstable1));
Assert.assertFalse(repairedContains(sstable2));
Assert.assertFalse(unrepairedContains(sstable2));
Assert.assertTrue(pendingContains(repairID, sstable2));
}
@Test
public void sstableRepairStatusChanged()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
// add as unrepaired
SSTableReader sstable = makeSSTable(false);
Assert.assertTrue(unrepairedContains(sstable));
Assert.assertFalse(repairedContains(sstable));
csm.getForPendingRepair(repairID).forEach(Assert::assertNull);
SSTableRepairStatusChanged notification;
// change to pending repaired
mutateRepaired(sstable, repairID);
notification = new SSTableRepairStatusChanged(Collections.singleton(sstable));
csm.handleNotification(notification, cfs.getTracker());
Assert.assertFalse(unrepairedContains(sstable));
Assert.assertFalse(repairedContains(sstable));
csm.getForPendingRepair(repairID).forEach(Assert::assertNotNull);
Assert.assertTrue(pendingContains(repairID, sstable));
// change to repaired
mutateRepaired(sstable, System.currentTimeMillis());
notification = new SSTableRepairStatusChanged(Collections.singleton(sstable));
csm.handleNotification(notification, cfs.getTracker());
Assert.assertFalse(unrepairedContains(sstable));
Assert.assertTrue(repairedContains(sstable));
Assert.assertFalse(pendingContains(repairID, sstable));
}
@Test
public void sstableDeleted()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
SSTableReader sstable = makeSSTable(true);
mutateRepaired(sstable, repairID);
csm.handleNotification(new SSTableAddedNotification(Collections.singleton(sstable)), cfs.getTracker());
Assert.assertTrue(pendingContains(repairID, sstable));
// delete sstable
SSTableDeletingNotification notification = new SSTableDeletingNotification(sstable);
csm.handleNotification(notification, cfs.getTracker());
Assert.assertFalse(pendingContains(repairID, sstable));
Assert.assertFalse(unrepairedContains(sstable));
Assert.assertFalse(repairedContains(sstable));
}
/**
* CompactionStrategyManager.getStrategies should include
* pending repair strategies when appropriate
*/
@Test
public void getStrategies()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
List<List<AbstractCompactionStrategy>> strategies;
strategies = csm.getStrategies();
Assert.assertEquals(3, strategies.size());
Assert.assertTrue(strategies.get(2).isEmpty());
SSTableReader sstable = makeSSTable(true);
mutateRepaired(sstable, repairID);
csm.handleNotification(new SSTableAddedNotification(Collections.singleton(sstable)), cfs.getTracker());
strategies = csm.getStrategies();
Assert.assertEquals(3, strategies.size());
Assert.assertFalse(strategies.get(2).isEmpty());
}
/**
* Tests that finalized repairs result in cleanup compaction tasks
* which reclassify the sstables as repaired
*/
@Test
public void cleanupCompactionFinalized()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
SSTableReader sstable = makeSSTable(true);
mutateRepaired(sstable, repairID);
csm.handleNotification(new SSTableAddedNotification(Collections.singleton(sstable)), cfs.getTracker());
LocalSessionAccessor.finalizeUnsafe(repairID);
csm.getForPendingRepair(repairID).forEach(Assert::assertNotNull);
Assert.assertNotNull(pendingContains(repairID, sstable));
Assert.assertTrue(sstable.isPendingRepair());
Assert.assertFalse(sstable.isRepaired());
cfs.getCompactionStrategyManager().enable(); // enable compaction to fetch next background task
AbstractCompactionTask compactionTask = csm.getNextBackgroundTask(FBUtilities.nowInSeconds());
Assert.assertNotNull(compactionTask);
Assert.assertSame(PendingRepairManager.RepairFinishedCompactionTask.class, compactionTask.getClass());
// run the compaction
compactionTask.execute(null);
Assert.assertTrue(repairedContains(sstable));
Assert.assertFalse(unrepairedContains(sstable));
csm.getForPendingRepair(repairID).forEach(Assert::assertNull);
// sstable should have pendingRepair cleared, and repairedAt set correctly
long expectedRepairedAt = ActiveRepairService.instance.getParentRepairSession(repairID).getRepairedAt();
Assert.assertFalse(sstable.isPendingRepair());
Assert.assertTrue(sstable.isRepaired());
Assert.assertEquals(expectedRepairedAt, sstable.getSSTableMetadata().repairedAt);
}
/**
* Tests that failed repairs result in cleanup compaction tasks
* which reclassify the sstables as unrepaired
*/
@Test
public void cleanupCompactionFailed()
{
UUID repairID = registerSession(cfs, true, true);
LocalSessionAccessor.prepareUnsafe(repairID, COORDINATOR, PARTICIPANTS);
SSTableReader sstable = makeSSTable(true);
mutateRepaired(sstable, repairID);
csm.handleNotification(new SSTableAddedNotification(Collections.singleton(sstable)), cfs.getTracker());
LocalSessionAccessor.failUnsafe(repairID);
csm.getForPendingRepair(repairID).forEach(Assert::assertNotNull);
Assert.assertNotNull(pendingContains(repairID, sstable));
Assert.assertTrue(sstable.isPendingRepair());
Assert.assertFalse(sstable.isRepaired());
cfs.getCompactionStrategyManager().enable(); // enable compaction to fetch next background task
AbstractCompactionTask compactionTask = csm.getNextBackgroundTask(FBUtilities.nowInSeconds());
Assert.assertNotNull(compactionTask);
Assert.assertSame(PendingRepairManager.RepairFinishedCompactionTask.class, compactionTask.getClass());
// run the compaction
compactionTask.execute(null);
Assert.assertFalse(repairedContains(sstable));
Assert.assertTrue(unrepairedContains(sstable));
csm.getForPendingRepair(repairID).forEach(Assert::assertNull);
// sstable should have pendingRepair cleared, and repairedAt set correctly
Assert.assertFalse(sstable.isPendingRepair());
Assert.assertFalse(sstable.isRepaired());
Assert.assertEquals(ActiveRepairService.UNREPAIRED_SSTABLE, sstable.getSSTableMetadata().repairedAt);
}
}