This is an automated email from the ASF dual-hosted git repository.

cshannon 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 7aba4f984a Update offline operation to wait for no opid or ecomp 
(#4414)
7aba4f984a is described below

commit 7aba4f984a35a4ae5c351b0b4716d4ed22151b51
Author: Christopher L. Shannon <cshan...@apache.org>
AuthorDate: Tue Mar 26 06:44:01 2024 -0400

    Update offline operation to wait for no opid or ecomp (#4414)
    
    Prior to elasticity a compaction or split could never run unless a
    tablet was hosted and how these operations can run on unhosted tablets.
    Taking a table offline should now wait for no OPID and ECOMP columns to
    exist in addition to the existing check for no location.
    
    This addresses a TODO in #3412
---
 .../core/clientImpl/TableOperationsImpl.java       |  12 ++-
 .../org/apache/accumulo/test/OfflineTableIT.java   | 119 +++++++++++++++++++++
 2 files changed, 128 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
 
b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
index 2ea43612f1..293177a557 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsImpl.java
@@ -27,11 +27,13 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toSet;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.AVAILABILITY;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.DIR;
+import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.ECOMP;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.HOSTING_REQUESTED;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LAST;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOGS;
+import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.OPID;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.PREV_ROW;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SUSPEND;
 import static 
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.TIME;
@@ -1305,19 +1307,23 @@ public class TableOperationsImpl extends 
TableOperationsHelper {
       Text continueRow = null;
       MapCounter<String> serverCounts = new MapCounter<>();
 
-      try (TabletsMetadata tablets = 
TabletsMetadata.builder(context).scanMetadataTable()
-          .overRange(range).fetch(AVAILABILITY, HOSTING_REQUESTED, LOCATION, 
PREV_ROW).build()) {
+      try (TabletsMetadata tablets =
+          TabletsMetadata.builder(context).scanMetadataTable().overRange(range)
+              .fetch(AVAILABILITY, HOSTING_REQUESTED, LOCATION, PREV_ROW, 
OPID, ECOMP).build()) {
 
         for (TabletMetadata tablet : tablets) {
           total++;
           Location loc = tablet.getLocation();
           TabletAvailability availability = tablet.getTabletAvailability();
+          var opid = tablet.getOperationId();
+          var externalCompactions = tablet.getExternalCompactions();
 
           if ((expectedState == TableState.ONLINE
               && (availability == TabletAvailability.HOSTED
                   || (availability == TabletAvailability.ONDEMAND) && 
tablet.getHostingRequested())
               && (loc == null || loc.getType() == LocationType.FUTURE))
-              || (expectedState == TableState.OFFLINE && loc != null)) {
+              || (expectedState == TableState.OFFLINE
+                  && (loc != null || opid != null || 
!externalCompactions.isEmpty()))) {
             if (continueRow == null) {
               continueRow = tablet.getExtent().toMetaRow();
             }
diff --git a/test/src/main/java/org/apache/accumulo/test/OfflineTableIT.java 
b/test/src/main/java/org/apache/accumulo/test/OfflineTableIT.java
index 1ddab10e52..9cdc2e3777 100644
--- a/test/src/main/java/org/apache/accumulo/test/OfflineTableIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/OfflineTableIT.java
@@ -18,19 +18,42 @@
  */
 package org.apache.accumulo.test;
 
+import static 
org.apache.accumulo.test.compaction.ExternalCompactionTestUtils.GROUP1;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.TableOfflineException;
 import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.fate.FateId;
+import org.apache.accumulo.core.fate.FateInstanceType;
+import org.apache.accumulo.core.metadata.ReferencedTabletFile;
+import org.apache.accumulo.core.metadata.schema.Ample.TabletsMutator;
+import org.apache.accumulo.core.metadata.schema.CompactionMetadata;
+import org.apache.accumulo.core.metadata.schema.ExternalCompactionId;
+import org.apache.accumulo.core.metadata.schema.TabletOperationId;
+import org.apache.accumulo.core.metadata.schema.TabletOperationType;
 import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.spi.compaction.CompactionKind;
+import org.apache.accumulo.core.spi.compaction.CompactorGroupId;
 import org.apache.accumulo.harness.MiniClusterConfigurationCallback;
 import org.apache.accumulo.harness.SharedMiniClusterBase;
 import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
+import org.apache.accumulo.server.ServerContext;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
@@ -104,4 +127,100 @@ public class OfflineTableIT extends SharedMiniClusterBase 
{
     }
   }
 
+  @Test
+  public void testEcompWaitForOffline() throws Exception {
+    final var ctx = getCluster().getServerContext();
+
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      ScanServerIT.createTableAndIngest(client, tableName, null, 10, 10, 
"colf");
+      TableId tableId = 
TableId.of(client.tableOperations().tableIdMap().get(tableName));
+      final var tabletMeta = ctx.getAmple().readTablet(new KeyExtent(tableId, 
null, null));
+
+      final var ecid = ExternalCompactionId.generate(UUID.randomUUID());
+
+      // Insert a fake External compaction which should prevent the wait for 
offline
+      // from returning
+      try (var mutator = ctx.getAmple().mutateTablets()) {
+        var tabletDir =
+            
tabletMeta.getFiles().stream().findFirst().orElseThrow().getPath().getParent();
+        var tmpFile = new Path(tabletDir, "C1234.rf_tmp");
+        var cm = new CompactionMetadata(tabletMeta.getFiles(), 
ReferencedTabletFile.of(tmpFile),
+            "localhost:16789", CompactionKind.SYSTEM, (short) 10, 
CompactorGroupId.of(GROUP1),
+            false, null);
+        
mutator.mutateTablet(tabletMeta.getExtent()).putExternalCompaction(ecid, 
cm).mutate();
+      }
+
+      // test the ecomp prevents the wait for the offline() table operation 
from finishing
+      // until the ecomp is deleted
+      testWaitForOffline(ctx, client, tableId, tableName, mutator -> mutator
+          
.mutateTablet(tabletMeta.getExtent()).deleteExternalCompaction(ecid).mutate());
+    }
+  }
+
+  @Test
+  public void testOpidWaitForOffline() throws Exception {
+    final var ctx = getCluster().getServerContext();
+
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      String tableName = getUniqueNames(1)[0];
+      ScanServerIT.createTableAndIngest(client, tableName, null, 10, 10, 
"colf");
+      TableId tableId = 
TableId.of(client.tableOperations().tableIdMap().get(tableName));
+      final var tabletMeta = ctx.getAmple().readTablet(new KeyExtent(tableId, 
null, null));
+
+      // Insert a fake opid to prevent going offline
+      try (var mutator = ctx.getAmple().mutateTablets()) {
+        mutator.mutateTablet(tabletMeta.getExtent())
+            .putOperation(TabletOperationId.from(TabletOperationType.SPLITTING,
+                FateId.from(FateInstanceType.META, UUID.randomUUID())))
+            .mutate();
+      }
+
+      // test the opid prevents the wait for the offline() table operation 
from finishing
+      // until the opid is deleted
+      testWaitForOffline(ctx, client, tableId, tableName,
+          mutator -> 
mutator.mutateTablet(tabletMeta.getExtent()).deleteOperation().mutate());
+    }
+  }
+
+  private void testWaitForOffline(ServerContext ctx, AccumuloClient client, 
TableId tableId,
+      String tableName, Consumer<TabletsMutator> clear) throws Exception {
+
+    // Try and take the table offline. At this point this should hang because 
there is a condition
+    // preventing waitForTableStateTransition call from returning (either opid 
or ecomp)
+    final var service = Executors.newSingleThreadExecutor();
+    try {
+      var tabletMeta = ctx.getAmple().readTablet(new KeyExtent(tableId, null, 
null));
+      Future<?> f = service.submit(() -> {
+        try {
+          client.tableOperations().offline(tableName, true);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      });
+
+      // Check that the wait times out for the offline operation
+      assertThrows(TimeoutException.class, () -> f.get(10, TimeUnit.SECONDS));
+
+      // Clear the condition that is preventing the 
waitForTableStateTransition method
+      // in TableOperationsImpl from finishing
+      try (var mutator = ctx.getAmple().mutateTablets()) {
+        clear.accept(mutator);
+      }
+
+      // The future should now finish and we should be offline
+      f.get();
+      tabletMeta = ctx.getAmple().readTablet(new KeyExtent(tableId, null, 
null));
+
+      // Should have no location, ecomp, or opid
+      assertFalse(tabletMeta.hasCurrent());
+      assertNull(tabletMeta.getLocation());
+      assertTrue(tabletMeta.getExternalCompactions().isEmpty());
+      assertNull(tabletMeta.getOperationId());
+      assertFalse(client.tableOperations().isOnline(tableName));
+    } finally {
+      service.shutdownNow();
+    }
+  }
+
 }

Reply via email to