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

Reply via email to