This is an automated email from the ASF dual-hosted git repository. cshannon pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/main by this push: new ce84c9d143 Add tests to VolumeIT for files with ranges (#3793) ce84c9d143 is described below commit ce84c9d1434d3dcf354f167b6dd6021e5c396d5d Author: Christopher L. Shannon <christopher.l.shan...@gmail.com> AuthorDate: Fri Oct 20 08:41:39 2023 -0400 Add tests to VolumeIT for files with ranges (#3793) This updates the volume replacement tests in VolumeIT to make sure that volume replacement works correctly with fenced files that have ranges set in metadata This addresses part of #3766 --- .../java/org/apache/accumulo/test/VolumeIT.java | 93 ++++++++++++++++++++-- .../accumulo/test/util/FileMetadataUtil.java | 38 ++++++--- 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/test/src/main/java/org/apache/accumulo/test/VolumeIT.java b/test/src/main/java/org/apache/accumulo/test/VolumeIT.java index 4f231deb6a..2e7dd8a5b0 100644 --- a/test/src/main/java/org/apache/accumulo/test/VolumeIT.java +++ b/test/src/main/java/org/apache/accumulo/test/VolumeIT.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.apache.accumulo.core.Constants; @@ -64,15 +65,20 @@ import org.apache.accumulo.core.dataImpl.KeyExtent; import org.apache.accumulo.core.metadata.MetadataTable; import org.apache.accumulo.core.metadata.RootTable; import org.apache.accumulo.core.metadata.StoredTabletFile; +import org.apache.accumulo.core.metadata.schema.DataFileValue; import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily; import org.apache.accumulo.core.security.Authorizations; +import org.apache.accumulo.core.security.TablePermission; import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; +import org.apache.accumulo.server.ServerContext; import org.apache.accumulo.server.init.Initialize; import org.apache.accumulo.server.log.WalStateManager; import org.apache.accumulo.server.log.WalStateManager.WalMarkerException; import org.apache.accumulo.server.log.WalStateManager.WalState; +import org.apache.accumulo.server.security.SystemCredentials; import org.apache.accumulo.server.util.Admin; import org.apache.accumulo.test.functional.ConfigurableMacBase; +import org.apache.accumulo.test.util.FileMetadataUtil; import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; @@ -278,6 +284,11 @@ public class VolumeIT extends ConfigurableMacBase { private void verifyVolumesUsed(AccumuloClient client, String tableName, boolean shouldExist, Path... paths) throws Exception { + verifyVolumesUsed(client, tableName, shouldExist, false, paths); + } + + private void verifyVolumesUsed(AccumuloClient client, String tableName, boolean shouldExist, + boolean rangedFiles, Path... paths) throws Exception { if (!client.tableOperations().exists(tableName)) { assertFalse(shouldExist); @@ -346,7 +357,10 @@ public class VolumeIT extends ConfigurableMacBase { sum += count; } - assertEquals(100, sum); + // When ranged files exist we there should be twice as many + // as the test split each file into 2 + int expectedCount = rangedFiles ? 200 : 100; + assertEquals(expectedCount, sum); } } @@ -392,7 +406,8 @@ public class VolumeIT extends ConfigurableMacBase { } } - private void testReplaceVolume(AccumuloClient client, boolean cleanShutdown) throws Exception { + private void testReplaceVolume(AccumuloClient client, boolean cleanShutdown, boolean rangedFiles) + throws Exception { String[] tableNames = getUniqueNames(3); verifyVolumesUsed(client, tableNames[0], false, v1, v2); @@ -403,6 +418,13 @@ public class VolumeIT extends ConfigurableMacBase { writeData(tableNames[1], c2); } + // If flag is true then for each file split and create two files + // to verify volume replacement works on files with ranges + if (rangedFiles) { + splitFilesWithRange(client, tableNames[0]); + splitFilesWithRange(client, tableNames[1]); + } + if (cleanShutdown) { assertEquals(0, cluster.exec(Admin.class, "stopAll").getProcess().waitFor()); } @@ -428,15 +450,16 @@ public class VolumeIT extends ConfigurableMacBase { // start cluster and verify that volumes were replaced cluster.start(); - verifyVolumesUsed(client, tableNames[0], true, v8, v9); - verifyVolumesUsed(client, tableNames[1], true, v8, v9); + verifyVolumesUsed(client, tableNames[0], true, rangedFiles, v8, v9); + verifyVolumesUsed(client, tableNames[1], true, rangedFiles, v8, v9); // verify writes to new dir client.tableOperations().compact(tableNames[0], null, null, true, true); client.tableOperations().compact(tableNames[1], null, null, true, true); - verifyVolumesUsed(client, tableNames[0], true, v8, v9); - verifyVolumesUsed(client, tableNames[1], true, v8, v9); + // Always pass false for ranged files as compaction will clean them up if exist + verifyVolumesUsed(client, tableNames[0], true, false, v8, v9); + verifyVolumesUsed(client, tableNames[1], true, false, v8, v9); client.tableOperations().compact(RootTable.NAME, new CompactionConfig().setWait(true)); @@ -465,14 +488,28 @@ public class VolumeIT extends ConfigurableMacBase { @Test public void testCleanReplaceVolumes() throws Exception { try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { - testReplaceVolume(client, true); + testReplaceVolume(client, true, false); } } @Test public void testDirtyReplaceVolumes() throws Exception { try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { - testReplaceVolume(client, false); + testReplaceVolume(client, false, false); + } + } + + @Test + public void testCleanReplaceVolumesWithRangedFiles() throws Exception { + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + testReplaceVolume(client, true, true); + } + } + + @Test + public void testDirtyReplaceVolumesWithRangedFiles() throws Exception { + try (AccumuloClient client = Accumulo.newClient().from(getClientProperties()).build()) { + testReplaceVolume(client, false, true); } } @@ -488,4 +525,44 @@ public class VolumeIT extends ConfigurableMacBase { config.write(out); } } + + // Go through each tablet file in metadata and split the files into two files + // by adding two new entries that covers half of the file. This will test that + // files with ranges work properly with volume replacement + private void splitFilesWithRange(AccumuloClient client, String tableName) throws Exception { + client.securityOperations().grantTablePermission(cluster.getConfig().getRootUserName(), + MetadataTable.NAME, TablePermission.WRITE); + final ServerContext ctx = getServerContext(); + ctx.setCredentials(new SystemCredentials(client.instanceOperations().getInstanceId(), "root", + new PasswordToken(ROOT_PASSWORD))); + + AtomicInteger i = new AtomicInteger(); + FileMetadataUtil.mutateTabletFiles(ctx, tableName, null, null, (tm, mutator, file, value) -> { + i.incrementAndGet(); + + // Create a mutation to delete the existing file metadata entry with infinite range + mutator.deleteFile(file); + + // Find the midpoint and create two new files, each with a range covering half the file + Text tabletMidPoint = getTabletMidPoint(tm.getExtent().endRow()); + // Handle edge case for last tablet + if (tabletMidPoint == null) { + tabletMidPoint = new Text( + String.format("%06d", Integer.parseInt(tm.getExtent().prevEndRow().toString()) + 50)); + } + + final DataFileValue newValue = new DataFileValue(Integer.max(1, (int) (value.getSize() / 2)), + Integer.max(1, (int) (value.getNumEntries() / 2))); + mutator.putFile(StoredTabletFile.of(file.getPath(), + new Range(tm.getExtent().prevEndRow(), tabletMidPoint)), newValue); + mutator.putFile( + StoredTabletFile.of(file.getPath(), new Range(tabletMidPoint, tm.getExtent().endRow())), + newValue); + }); + } + + private static Text getTabletMidPoint(Text row) { + return row != null ? new Text(String.format("%06d", Integer.parseInt(row.toString()) - 50)) + : null; + } } diff --git a/test/src/main/java/org/apache/accumulo/test/util/FileMetadataUtil.java b/test/src/main/java/org/apache/accumulo/test/util/FileMetadataUtil.java index b4bc67d854..9502dca9d9 100644 --- a/test/src/main/java/org/apache/accumulo/test/util/FileMetadataUtil.java +++ b/test/src/main/java/org/apache/accumulo/test/util/FileMetadataUtil.java @@ -102,8 +102,25 @@ public class FileMetadataUtil { public static void splitFilesIntoRanges(final ServerContext ctx, String tableName, Text tabletStartRow, Text tabletEndRow, Set<Range> fileRanges) throws Exception { + Preconditions.checkArgument(!fileRanges.isEmpty(), "Ranges must not be empty"); + mutateTabletFiles(ctx, tableName, tabletStartRow, tabletEndRow, (tm, mutator, file, value) -> { + // Create a mutation to delete the existing file metadata entry with infinite range + mutator.deleteFile(file); + + fileRanges.forEach(range -> { + final DataFileValue newValue = + new DataFileValue(Integer.max(1, (int) (value.getSize() / fileRanges.size())), + Integer.max(1, (int) (value.getNumEntries() / fileRanges.size()))); + mutator.putFile(StoredTabletFile.of(file.getPath(), range), newValue); + }); + }); + } + + public static void mutateTabletFiles(final ServerContext ctx, String tableName, + Text tabletStartRow, Text tabletEndRow, FileMutator fileMutator) throws Exception { + final TableId tableId = TableId.of(ctx.tableOperations().tableIdMap().get(tableName)); // Bring tablet offline so we can modify file metadata @@ -113,35 +130,32 @@ public class FileMetadataUtil { ctx.getAmple().readTablets().forTable(tableId).overlapping(tabletStartRow, tabletEndRow) .fetch(ColumnType.FILES, ColumnType.PREV_ROW).build()) { - // Read each file and split to 10 ranges + // Process each tablet in the given start/end row range for (TabletMetadata tabletMetadata : tabletsMetadata) { final KeyExtent ke = tabletMetadata.getExtent(); - // Create a mutation to delete the existing file metadata entry with infinite range TabletMutator mutator = ctx.getAmple().mutateTablet(ke); - // Read each files and split into the given ranges + // Read each file and mutate for (Entry<StoredTabletFile,DataFileValue> fileEntry : tabletMetadata.getFilesMap() .entrySet()) { StoredTabletFile file = fileEntry.getKey(); DataFileValue value = fileEntry.getValue(); - // Create a mutation to delete the existing file metadata entry with infinite range - mutator.deleteFile(file); - - fileRanges.forEach(range -> { - final DataFileValue newValue = - new DataFileValue(Integer.max(1, (int) (value.getSize() / fileRanges.size())), - Integer.max(1, (int) (value.getNumEntries() / fileRanges.size()))); - mutator.putFile(StoredTabletFile.of(file.getPath(), range), newValue); - }); + // do any file mutations + fileMutator.mutate(tabletMetadata, mutator, file, value); mutator.mutate(); } } } + // Bring back online after metadata updates ctx.tableOperations().online(tableName, true); } + public interface FileMutator { + void mutate(TabletMetadata tm, TabletMutator mutator, StoredTabletFile file, + DataFileValue value); + } }