This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch elasticity in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/elasticity by this push: new 53d4f29bcf Rewrote testFatePrintAndSummaryCommandsWithInProgressTxns for elasticity (#4683) 53d4f29bcf is described below commit 53d4f29bcf1135aceb04e7ba48cffff7b5e372bf Author: Kevin Rathbun <43969518+kevinrr...@users.noreply.github.com> AuthorDate: Thu Jun 27 15:20:13 2024 -0400 Rewrote testFatePrintAndSummaryCommandsWithInProgressTxns for elasticity (#4683) testFatePrintAndSummaryCommandsWithInProgressTxns was merged into 2.1 and 3.1 but dropped from elasticity due to requiring rewrite --- .../org/apache/accumulo/core/fate/AdminUtil.java | 73 ++++++--------- .../accumulo/test/fate/FateOpsCommandsIT.java | 101 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/fate/AdminUtil.java b/core/src/main/java/org/apache/accumulo/core/fate/AdminUtil.java index bad466ba75..2868ae8645 100644 --- a/core/src/main/java/org/apache/accumulo/core/fate/AdminUtil.java +++ b/core/src/main/java/org/apache/accumulo/core/fate/AdminUtil.java @@ -351,10 +351,10 @@ public class AdminUtil<T> { * @param waitingLocks populated list of locks held by transaction - or an empty map if none. * @return current fate and lock status */ - private FateStatus getTransactionStatus(Map<FateInstanceType,ReadOnlyFateStore<T>> fateStores, - Set<FateId> fateIdFilter, EnumSet<TStatus> statusFilter, - EnumSet<FateInstanceType> typesFilter, Map<FateId,List<String>> heldLocks, - Map<FateId,List<String>> waitingLocks) { + public static <T> FateStatus getTransactionStatus( + Map<FateInstanceType,ReadOnlyFateStore<T>> fateStores, Set<FateId> fateIdFilter, + EnumSet<TStatus> statusFilter, EnumSet<FateInstanceType> typesFilter, + Map<FateId,List<String>> heldLocks, Map<FateId,List<String>> waitingLocks) { final List<TransactionStatus> statuses = new ArrayList<>(); fateStores.forEach((type, store) -> { @@ -362,53 +362,34 @@ public class AdminUtil<T> { fateIds.forEach(fateId -> { ReadOnlyFateTxStore<T> txStore = store.read(fateId); - try { - String txName = (String) txStore.getTransactionInfo(Fate.TxInfo.TX_NAME); + String txName = (String) txStore.getTransactionInfo(Fate.TxInfo.TX_NAME); - List<String> hlocks = heldLocks.remove(fateId); + List<String> hlocks = heldLocks.remove(fateId); - if (hlocks == null) { - hlocks = Collections.emptyList(); - } + if (hlocks == null) { + hlocks = Collections.emptyList(); + } - List<String> wlocks = waitingLocks.remove(fateId); + List<String> wlocks = waitingLocks.remove(fateId); - if (wlocks == null) { - wlocks = Collections.emptyList(); - } + if (wlocks == null) { + wlocks = Collections.emptyList(); + } - String top = null; - ReadOnlyRepo<T> repo = txStore.top(); - if (repo != null) { - top = repo.getName(); - } + String top = null; + ReadOnlyRepo<T> repo = txStore.top(); + if (repo != null) { + top = repo.getName(); + } - TStatus status = txStore.getStatus(); + TStatus status = txStore.getStatus(); - long timeCreated = txStore.timeCreated(); + long timeCreated = txStore.timeCreated(); - if (includeByStatus(status, statusFilter) && includeByFateId(fateId, fateIdFilter) - && includeByInstanceType(fateId.getType(), typesFilter)) { - statuses.add(new TransactionStatus(fateId, type, status, txName, hlocks, wlocks, top, - timeCreated)); - } - } catch (Exception e) { - // If the cause of the Exception is a NoNodeException, it should be ignored as this - // indicates the transaction has completed between the time the list of transactions was - // acquired and the time the transaction was probed for info. - boolean nne = false; - Throwable cause = e; - while (cause != null) { - if (cause instanceof KeeperException.NoNodeException) { - nne = true; - break; - } - cause = cause.getCause(); - } - if (!nne) { - throw e; - } - log.debug("Tried to get info on a since completed transaction - ignoring {} ", fateId); + if (includeByStatus(status, statusFilter) && includeByFateId(fateId, fateIdFilter) + && includeByInstanceType(fateId.getType(), typesFilter)) { + statuses.add(new TransactionStatus(fateId, type, status, txName, hlocks, wlocks, top, + timeCreated)); } }); } @@ -416,15 +397,15 @@ public class AdminUtil<T> { return new FateStatus(statuses, heldLocks, waitingLocks); } - private boolean includeByStatus(TStatus status, EnumSet<TStatus> statusFilter) { + private static boolean includeByStatus(TStatus status, EnumSet<TStatus> statusFilter) { return statusFilter == null || statusFilter.isEmpty() || statusFilter.contains(status); } - private boolean includeByFateId(FateId fateId, Set<FateId> fateIdFilter) { + private static boolean includeByFateId(FateId fateId, Set<FateId> fateIdFilter) { return fateIdFilter == null || fateIdFilter.isEmpty() || fateIdFilter.contains(fateId); } - private boolean includeByInstanceType(FateInstanceType type, + private static boolean includeByInstanceType(FateInstanceType type, EnumSet<FateInstanceType> typesFilter) { return typesFilter == null || typesFilter.isEmpty() || typesFilter.contains(type); } diff --git a/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsIT.java b/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsIT.java index f448149d76..04bff32cd2 100644 --- a/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsIT.java +++ b/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsIT.java @@ -19,6 +19,9 @@ package org.apache.accumulo.test.fate; import static org.apache.accumulo.core.util.compaction.ExternalCompactionUtil.getCompactorAddrs; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -26,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.lang.reflect.Method; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -38,17 +42,24 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.Accumulo; import org.apache.accumulo.core.client.AccumuloClient; import org.apache.accumulo.core.client.IteratorSetting; import org.apache.accumulo.core.client.admin.NewTableConfiguration; +import org.apache.accumulo.core.clientImpl.ClientContext; import org.apache.accumulo.core.conf.ConfigurationCopy; import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.fate.AbstractFateStore; +import org.apache.accumulo.core.fate.AdminUtil; import org.apache.accumulo.core.fate.Fate; import org.apache.accumulo.core.fate.FateId; import org.apache.accumulo.core.fate.FateInstanceType; import org.apache.accumulo.core.fate.FateStore; +import org.apache.accumulo.core.fate.MetaFateStore; import org.apache.accumulo.core.fate.ReadOnlyFateStore; +import org.apache.accumulo.core.fate.user.UserFateStore; +import org.apache.accumulo.core.fate.zookeeper.ZooReaderWriter; import org.apache.accumulo.core.iterators.IteratorUtil; import org.apache.accumulo.minicluster.ServerType; import org.apache.accumulo.miniclusterImpl.MiniAccumuloClusterImpl.ProcessInfo; @@ -62,6 +73,7 @@ import org.apache.accumulo.test.functional.ReadWriteIT; import org.apache.accumulo.test.functional.SlowIterator; import org.apache.accumulo.test.util.Wait; import org.apache.hadoop.conf.Configuration; +import org.easymock.EasyMock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -592,6 +604,95 @@ public abstract class FateOpsCommandsIT extends ConfigurableMacBase fate.shutdown(10, TimeUnit.MINUTES); } + @Test + public void testFatePrintAndSummaryCommandsWithInProgressTxns() throws Exception { + executeTest(this::testFatePrintAndSummaryCommandsWithInProgressTxns); + } + + protected void testFatePrintAndSummaryCommandsWithInProgressTxns(FateStore<TestEnv> store, + ServerContext sctx) throws Exception { + // This test was written for an issue with the 'admin fate --print' and 'admin fate --summary' + // commands where transactions could complete mid-print causing the command to fail. These + // commands first get a list of the transactions and then probe for info on the transactions. + // If a transaction completed between getting the list and probing for info on that + // transaction, the command would fail. This test ensures that this problem has been fixed + // (if the tx no longer exists, it should just be ignored so the print/summary can complete). + FateStore<TestEnv> mockedStore; + + // This error was occurring in AdminUtil.getTransactionStatus(), so we will test this method. + if (store.type().equals(FateInstanceType.USER)) { + Method listMethod = UserFateStore.class.getMethod("list"); + mockedStore = + EasyMock.createMockBuilder(UserFateStore.class).withConstructor(ClientContext.class) + .withArgs(sctx).addMockedMethod(listMethod).createMock(); + } else { + Method listMethod = MetaFateStore.class.getMethod("list"); + mockedStore = EasyMock.createMockBuilder(MetaFateStore.class) + .withConstructor(String.class, ZooReaderWriter.class) + .withArgs(sctx.getZooKeeperRoot() + Constants.ZFATE, sctx.getZooReaderWriter()) + .addMockedMethod(listMethod).createMock(); + } + + // 3 FateIds, two that exist and one that does not. We are simulating that a transaction that + // doesn't exist is accessed in getTransactionStatus() and ensuring that this doesn't cause + // the method to fail or have any unexpected behavior. + FateId tx1 = store.create(); + FateId tx2 = FateId.from(store.type(), UUID.randomUUID()); + FateId tx3 = store.create(); + + List<ReadOnlyFateStore.FateIdStatus> fateIdStatusList = + List.of(createFateIdStatus(tx1), createFateIdStatus(tx2), createFateIdStatus(tx3)); + expect(mockedStore.list()).andReturn(fateIdStatusList.stream()).once(); + + replay(mockedStore); + + AdminUtil.FateStatus status = null; + try { + status = AdminUtil.getTransactionStatus(Map.of(store.type(), mockedStore), null, null, null, + new HashMap<>(), new HashMap<>()); + } catch (Exception e) { + fail("An unexpected error occurred in getTransactionStatus():\n" + e); + } + + verify(mockedStore); + + assertNotNull(status); + // All three should be returned + assertEquals(3, status.getTransactions().size()); + assertEquals(status.getTransactions().stream().map(AdminUtil.TransactionStatus::getFateId) + .collect(Collectors.toList()), List.of(tx1, tx2, tx3)); + // The two real FateIds should have NEW status and the fake one should be UNKNOWN + assertEquals( + status.getTransactions().stream().map(AdminUtil.TransactionStatus::getStatus) + .collect(Collectors.toList()), + List.of(ReadOnlyFateStore.TStatus.NEW, ReadOnlyFateStore.TStatus.UNKNOWN, + ReadOnlyFateStore.TStatus.NEW)); + // None of them should have a name since none of them were seeded with work + assertEquals(status.getTransactions().stream().map(AdminUtil.TransactionStatus::getTxName) + .collect(Collectors.toList()), Arrays.asList(null, null, null)); + // None of them should have a Repo since none of them were seeded with work + assertEquals(status.getTransactions().stream().map(AdminUtil.TransactionStatus::getTop) + .collect(Collectors.toList()), Arrays.asList(null, null, null)); + // The FateId that doesn't exist should have a creation time of 0, the others should not + List<Long> timeCreated = status.getTransactions().stream() + .map(AdminUtil.TransactionStatus::getTimeCreated).collect(Collectors.toList()); + assertNotEquals(timeCreated.get(0), 0); + assertEquals(timeCreated.get(1), 0); + assertNotEquals(timeCreated.get(2), 0); + // All should have the store.type() type + assertEquals(status.getTransactions().stream().map(AdminUtil.TransactionStatus::getInstanceType) + .collect(Collectors.toList()), List.of(store.type(), store.type(), store.type())); + } + + private ReadOnlyFateStore.FateIdStatus createFateIdStatus(FateId fateId) { + return new AbstractFateStore.FateIdStatusBase(fateId) { + @Override + public ReadOnlyFateStore.TStatus getStatus() { + return null; + } + }; + } + /** * * @param printResult the output of the --print fate command