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(); + } + } + }