This is an automated email from the ASF dual-hosted git repository.
ddanielr pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.1 by this push:
new b874c0ed66 Adds --dry-run option to Merge (#6437)
b874c0ed66 is described below
commit b874c0ed66debf54378ab2971e46f77909f2f7ea
Author: Daniel Roberts <[email protected]>
AuthorDate: Mon Jun 22 15:14:18 2026 -0400
Adds --dry-run option to Merge (#6437)
* Adds --dry-run option to Merge
Adds the --dry-run option to both the Shell's MergeCommand and the Merge
utility code.
Fixes an issue where MergeExceptions were being wrapped needlessly.
Adds Mock tests for MergeCommand.
Adds MergeIT tests.
Adds explicit failure message for attempting to merge the accumulo.root
table instead of just silently doing nothing.
---
.../java/org/apache/accumulo/core/util/Merge.java | 58 +++++++----
.../org/apache/accumulo/core/util/MergeTest.java | 20 ++--
.../accumulo/shell/commands/MergeCommand.java | 68 +++++++++----
.../accumulo/shell/commands/MergeCommandTest.java | 113 +++++++++++++++++++++
.../apache/accumulo/test/functional/MergeIT.java | 75 +++++++++++++-
5 files changed, 285 insertions(+), 49 deletions(-)
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Merge.java
b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
index 41f5a67943..9bd5dcc673 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Merge.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
@@ -37,6 +37,7 @@ import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.TableId;
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.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
import org.apache.accumulo.core.trace.TraceUtil;
@@ -63,7 +64,7 @@ public class Merge {
private static final Logger log = LoggerFactory.getLogger(Merge.class);
protected void message(String format, Object... args) {
- log.info(String.format(format, args));
+ log.info("{}", String.format(format, args));
}
public static class MemoryConverter implements IStringConverter<Long> {
@@ -95,6 +96,9 @@ public class Merge {
Text begin = null;
@Parameter(names = {"-e", "--end"}, description = "end tablet", converter
= TextConverter.class)
Text end = null;
+ @Parameter(names = {"--dry-run"},
+ description = "Will only list which tablets ranges it plans on
merging. No merge will be performed")
+ boolean dryRun = false;
}
public void start(String[] args) throws MergeException {
@@ -112,11 +116,18 @@ public class Merge {
if (opts.goalSize == null || opts.goalSize < 1) {
AccumuloConfiguration tableConfig =
new
ConfigurationCopy(client.tableOperations().getConfiguration(opts.tableName));
- opts.goalSize =
tableConfig.getAsBytes(Property.TABLE_SPLIT_THRESHOLD);
+ long newGoalSize =
tableConfig.getAsBytes(Property.TABLE_SPLIT_THRESHOLD);
+ message("Invalid goal size: " + opts.goalSize + " Using the "
+ + Property.TABLE_SPLIT_THRESHOLD.getKey() + " value of : " +
newGoalSize);
+ opts.goalSize = newGoalSize;
}
message("Merging tablets in table %s to %d bytes", opts.tableName,
opts.goalSize);
- mergomatic(client, opts.tableName, opts.begin, opts.end,
opts.goalSize, opts.force);
+ mergomatic(client, opts.tableName, opts.begin, opts.end,
opts.goalSize, opts.force,
+ opts.dryRun);
+ } catch (MergeException e) {
+ TraceUtil.setException(span, e, true);
+ throw e;
} catch (Exception ex) {
TraceUtil.setException(span, ex, true);
throw new MergeException(ex);
@@ -142,10 +153,10 @@ public class Merge {
}
public void mergomatic(AccumuloClient client, String table, Text start, Text
end, long goalSize,
- boolean force) throws MergeException {
+ boolean force, boolean dryRun) throws MergeException {
try {
- if (table.equals(MetadataTable.NAME)) {
- throw new IllegalArgumentException("cannot merge tablets on the
metadata table");
+ if (table.equals(MetadataTable.NAME) || table.equals(RootTable.NAME)) {
+ throw new IllegalArgumentException("cannot merge tablets on the " +
table + " table");
}
List<Size> sizes = new ArrayList<>();
long totalSize = 0;
@@ -156,11 +167,11 @@ public class Merge {
totalSize += next.size;
sizes.add(next);
if (totalSize > goalSize) {
- totalSize = mergeMany(client, table, sizes, goalSize, force, false);
+ totalSize = mergeMany(client, table, sizes, goalSize, force, false,
dryRun);
}
}
if (sizes.size() > 1) {
- mergeMany(client, table, sizes, goalSize, force, true);
+ mergeMany(client, table, sizes, goalSize, force, true, dryRun);
}
} catch (Exception ex) {
throw new MergeException(ex);
@@ -168,7 +179,7 @@ public class Merge {
}
protected long mergeMany(AccumuloClient client, String table, List<Size>
sizes, long goalSize,
- boolean force, boolean last) throws MergeException {
+ boolean force, boolean last, boolean dryRun) throws MergeException {
// skip the big tablets, which will be the typical case
while (!sizes.isEmpty()) {
if (sizes.get(0).size < goalSize) {
@@ -192,13 +203,13 @@ public class Merge {
}
if (numToMerge > 1) {
- mergeSome(client, table, sizes, numToMerge);
+ mergeSome(client, table, sizes, numToMerge, dryRun);
} else {
if (numToMerge == 1 && sizes.size() > 1) {
// here we have the case of a merge candidate that is surrounded by
candidates that would
// split
if (force) {
- mergeSome(client, table, sizes, 2);
+ mergeSome(client, table, sizes, 2, dryRun);
} else {
sizes.remove(0);
}
@@ -206,7 +217,7 @@ public class Merge {
}
if (numToMerge == 0 && sizes.size() > 1 && last) {
// That's the last tablet, and we have a bunch to merge
- mergeSome(client, table, sizes, sizes.size());
+ mergeSome(client, table, sizes, sizes.size(), dryRun);
}
long result = 0;
for (Size s : sizes) {
@@ -215,16 +226,16 @@ public class Merge {
return result;
}
- protected void mergeSome(AccumuloClient client, String table, List<Size>
sizes, int numToMerge)
- throws MergeException {
- merge(client, table, sizes, numToMerge);
+ protected void mergeSome(AccumuloClient client, String table, List<Size>
sizes, int numToMerge,
+ boolean dryRun) throws MergeException {
+ merge(client, table, sizes, numToMerge, dryRun);
for (int i = 0; i < numToMerge; i++) {
sizes.remove(0);
}
}
- protected void merge(AccumuloClient client, String table, List<Size> sizes,
int numToMerge)
- throws MergeException {
+ protected void merge(AccumuloClient client, String table, List<Size> sizes,
int numToMerge,
+ boolean dryRun) throws MergeException {
try {
Text start = sizes.get(0).extent.prevEndRow();
Text end = sizes.get(numToMerge - 1).extent.endRow();
@@ -233,13 +244,22 @@ public class Merge {
: Key.toPrintableString(start.getBytes(), 0, start.getLength(),
start.getLength()),
end == null ? "+inf"
: Key.toPrintableString(end.getBytes(), 0, end.getLength(),
end.getLength()));
+ if (dryRun) {
+ message("dry-run would have started a Fate Merge for table %s tablet
range (%s to %s]",
+ table,
+ start == null ? "-inf"
+ : Key.toPrintableString(start.getBytes(), 0,
start.getLength(), start.getLength()),
+ end == null ? "+inf"
+ : Key.toPrintableString(end.getBytes(), 0, end.getLength(),
end.getLength()));
+ return;
+ }
client.tableOperations().merge(table, start, end);
} catch (Exception ex) {
throw new MergeException(ex);
}
}
- protected Iterator<Size> getSizeIterator(AccumuloClient client, String
tablename, Text start,
+ protected Iterator<Size> getSizeIterator(AccumuloClient client, String
tableName, Text start,
Text end) throws MergeException {
// open up metadata, walk through the tablets.
@@ -247,7 +267,7 @@ public class Merge {
TabletsMetadata tablets;
try {
ClientContext context = (ClientContext) client;
- tableId = context.getTableId(tablename);
+ tableId = context.getTableId(tableName);
tablets = TabletsMetadata.builder(context).scanMetadataTable()
.overRange(new KeyExtent(tableId, end,
start).toMetaRange()).fetch(FILES, PREV_ROW)
.build();
diff --git a/core/src/test/java/org/apache/accumulo/core/util/MergeTest.java
b/core/src/test/java/org/apache/accumulo/core/util/MergeTest.java
index 6d253fe0ae..ad1a699370 100644
--- a/core/src/test/java/org/apache/accumulo/core/util/MergeTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/util/MergeTest.java
@@ -57,7 +57,7 @@ public class MergeTest {
protected void message(String format, Object... args) {}
@Override
- protected Iterator<Size> getSizeIterator(AccumuloClient client, String
tablename,
+ protected Iterator<Size> getSizeIterator(AccumuloClient client, String
tableName,
final Text start, final Text end) throws MergeException {
final Iterator<Size> impl = tablets.iterator();
return new Iterator<>() {
@@ -103,8 +103,8 @@ public class MergeTest {
}
@Override
- protected void merge(AccumuloClient client, String table, List<Size>
sizes, int numToMerge)
- throws MergeException {
+ protected void merge(AccumuloClient client, String table, List<Size>
sizes, int numToMerge,
+ boolean dryRun) throws MergeException {
List<Size> merge = new ArrayList<>();
for (int i = 0; i < numToMerge; i++) {
merge.add(sizes.get(i));
@@ -127,20 +127,20 @@ public class MergeTest {
// Merge everything to the last tablet
int i;
MergeTester test = new MergeTester(10, 20, 30);
- test.mergomatic(null, "table", null, null, 1000, false);
+ test.mergomatic(null, "table", null, null, 1000, false, false);
assertEquals(1, test.merges.size());
assertArrayEquals(new int[] {10, 20, 30}, sizes(test.merges.get(i = 0)));
// Merge ranges around tablets that are big enough
test = new MergeTester(1, 2, 100, 1000, 17, 1000, 4, 5, 6, 900);
- test.mergomatic(null, "table", null, null, 1000, false);
+ test.mergomatic(null, "table", null, null, 1000, false, false);
assertEquals(2, test.merges.size());
assertArrayEquals(new int[] {1, 2, 100}, sizes(test.merges.get(i = 0)));
assertArrayEquals(new int[] {4, 5, 6, 900}, sizes(test.merges.get(++i)));
// Test the force option
test = new MergeTester(1, 2, 100, 1000, 17, 1000, 4, 5, 6, 900);
- test.mergomatic(null, "table", null, null, 1000, true);
+ test.mergomatic(null, "table", null, null, 1000, true, false);
assertEquals(3, test.merges.size());
assertArrayEquals(new int[] {1, 2, 100}, sizes(test.merges.get(i = 0)));
assertArrayEquals(new int[] {17, 1000}, sizes(test.merges.get(++i)));
@@ -148,25 +148,25 @@ public class MergeTest {
// Limit the low-end of the merges
test = new MergeTester(1, 2, 1000, 17, 1000, 4, 5, 6, 900);
- test.mergomatic(null, "table", new Text("00004"), null, 1000, false);
+ test.mergomatic(null, "table", new Text("00004"), null, 1000, false,
false);
assertEquals(1, test.merges.size());
assertArrayEquals(new int[] {4, 5, 6, 900}, sizes(test.merges.get(i = 0)));
// Limit the upper end of the merges
test = new MergeTester(1, 2, 1000, 17, 1000, 4, 5, 6, 900);
- test.mergomatic(null, "table", null, new Text("00004"), 1000, false);
+ test.mergomatic(null, "table", null, new Text("00004"), 1000, false,
false);
assertEquals(1, test.merges.size());
assertArrayEquals(new int[] {1, 2}, sizes(test.merges.get(i = 0)));
// Limit both ends
test = new MergeTester(1, 2, 1000, 17, 1000, 4, 5, 6, 900);
- test.mergomatic(null, "table", new Text("00002"), new Text("00004"), 1000,
true);
+ test.mergomatic(null, "table", new Text("00002"), new Text("00004"), 1000,
true, false);
assertEquals(1, test.merges.size());
assertArrayEquals(new int[] {17, 1000}, sizes(test.merges.get(i = 0)));
// Clump up tablets into larger values
test = new MergeTester(100, 250, 500, 600, 100, 200, 500, 200);
- test.mergomatic(null, "table", null, null, 1000, false);
+ test.mergomatic(null, "table", null, null, 1000, false, false);
assertEquals(3, test.merges.size());
assertArrayEquals(new int[] {100, 250, 500}, sizes(test.merges.get(i =
0)));
assertArrayEquals(new int[] {600, 100, 200}, sizes(test.merges.get(++i)));
diff --git
a/shell/src/main/java/org/apache/accumulo/shell/commands/MergeCommand.java
b/shell/src/main/java/org/apache/accumulo/shell/commands/MergeCommand.java
index d6a74fce23..139402c0d0 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/commands/MergeCommand.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/commands/MergeCommand.java
@@ -18,7 +18,11 @@
*/
package org.apache.accumulo.shell.commands;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
+import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.util.Merge;
import org.apache.accumulo.shell.Shell;
import org.apache.accumulo.shell.Shell.Command;
@@ -28,7 +32,7 @@ import org.apache.commons.cli.Options;
import org.apache.hadoop.io.Text;
public class MergeCommand extends Command {
- private Option verboseOpt, forceOpt, sizeOpt, allOpt;
+ private Option verboseOpt, forceOpt, sizeOpt, allOpt, dryRunOpt;
@Override
public int execute(final String fullCommand, final CommandLine cl, final
Shell shellState)
@@ -36,6 +40,7 @@ public class MergeCommand extends Command {
boolean verbose = shellState.isVerbose();
boolean force = false;
boolean all = false;
+ boolean dryRun = false;
long size = -1;
final String tableName = OptUtil.getTableOpt(cl, shellState);
final Text startRow = OptUtil.getStartRow(cl);
@@ -52,6 +57,9 @@ public class MergeCommand extends Command {
if (cl.hasOption(sizeOpt.getOpt())) {
size =
ConfigurationTypeHelper.getFixedMemoryAsBytes(cl.getOptionValue(sizeOpt.getOpt()));
}
+ if (cl.hasOption(dryRunOpt)) {
+ dryRun = true;
+ }
if (startRow == null && endRow == null && size < 0 && !all) {
if (!shellState
.confirm(" Warning!!! Are you REALLY sure you want to merge the
entire table { "
@@ -60,21 +68,7 @@ public class MergeCommand extends Command {
return 0;
}
}
- if (size < 0) {
- shellState.getAccumuloClient().tableOperations().merge(tableName,
startRow, endRow);
- } else {
- final boolean finalVerbose = verbose;
- final Merge merge = new Merge() {
- @Override
- protected void message(String fmt, Object... args) {
- if (finalVerbose) {
- shellState.getWriter().println(String.format(fmt, args));
- }
- }
- };
- merge.mergomatic(shellState.getAccumuloClient(), tableName, startRow,
endRow, size, force);
- }
- return 0;
+ return executeMerge(shellState, tableName, startRow, endRow, size,
verbose, force, dryRun);
}
@Override
@@ -96,10 +90,14 @@ public class MergeCommand extends Command {
forceOpt = new Option("f", "force", false,
"merge small tablets to large tablets, even if it goes over the given
size");
// Using the constructor does not allow for empty option
- Option.Builder builder = Option.builder().longOpt("all").hasArg(false)
+ Option.Builder allBuilder = Option.builder().longOpt("all").hasArg(false)
.desc("allow an entire table to be merged into one tablet without
prompting"
+ " the user for confirmation");
- allOpt = builder.build();
+ allOpt = allBuilder.build();
+ Option.Builder dryRunBuilder =
Option.builder().longOpt("dry-run").hasArg(false)
+ .desc("print the ranges it will merge, but do not perform any merge
operations");
+ dryRunOpt = dryRunBuilder.build();
+
o.addOption(OptUtil.startRowOpt());
o.addOption(OptUtil.endRowOpt());
o.addOption(OptUtil.tableOpt("table to be merged"));
@@ -107,7 +105,41 @@ public class MergeCommand extends Command {
o.addOption(sizeOpt);
o.addOption(forceOpt);
o.addOption(allOpt);
+ o.addOption(dryRunOpt);
return o;
}
+ // This method is stubbed out to allow for mock testing
+ int executeMerge(Shell shellState, String tableName, Text startRow, Text
endRow, long size,
+ boolean verbose, boolean force, boolean dryRun) throws AccumuloException,
+ TableNotFoundException, AccumuloSecurityException, Merge.MergeException {
+ if (size < 0) {
+ if (dryRun) {
+ shellState.getWriter()
+ .println(String.format(
+ "dry-run would have started a Fate Merge for table %s tablet
range (%s to %s]",
+ tableName,
+ startRow == null ? "-inf"
+ : Key.toPrintableString(startRow.getBytes(), 0,
startRow.getLength(),
+ startRow.getLength()),
+ endRow == null ? "+inf" :
Key.toPrintableString(endRow.getBytes(), 0,
+ endRow.getLength(), endRow.getLength())));
+ return 0;
+ }
+ shellState.getAccumuloClient().tableOperations().merge(tableName,
startRow, endRow);
+ } else {
+ final boolean finalVerbose = verbose;
+ final Merge merge = new Merge() {
+ @Override
+ protected void message(String fmt, Object... args) {
+ if (finalVerbose) {
+ shellState.getWriter().println(String.format(fmt, args));
+ }
+ }
+ };
+ merge.mergomatic(shellState.getAccumuloClient(), tableName, startRow,
endRow, size, force,
+ dryRun);
+ }
+ return 0;
+ }
}
diff --git
a/shell/src/test/java/org/apache/accumulo/shell/commands/MergeCommandTest.java
b/shell/src/test/java/org/apache/accumulo/shell/commands/MergeCommandTest.java
index 42c85f30a3..4762553299 100644
---
a/shell/src/test/java/org/apache/accumulo/shell/commands/MergeCommandTest.java
+++
b/shell/src/test/java/org/apache/accumulo/shell/commands/MergeCommandTest.java
@@ -18,12 +18,61 @@
*/
package org.apache.accumulo.shell.commands;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.newCapture;
+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.assertTrue;
+import java.io.PrintWriter;
+import java.util.Optional;
+
+import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.admin.InstanceOperations;
+import org.apache.accumulo.core.client.admin.TableOperations;
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.shell.Shell;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.hadoop.io.Text;
+import org.easymock.Capture;
import org.junit.jupiter.api.Test;
public class MergeCommandTest {
+ public static class TestMergeCommand extends MergeCommand {
+ @Override
+ int executeMerge(Shell shellState, String tableName, Text startRow, Text
endRow, long size,
+ boolean verbose, boolean force, boolean dryRun)
+ throws AccumuloException, TableNotFoundException,
AccumuloSecurityException {
+ if (dryRun) {
+ shellState.getWriter()
+ .println(String.format(
+ "dry-run would have started a Fate Merge for table %s tablet
range (%s to %s]",
+ tableName,
+ startRow == null ? "-inf"
+ : Key.toPrintableString(startRow.getBytes(), 0,
startRow.getLength(),
+ startRow.getLength()),
+ endRow == null ? "+inf" :
Key.toPrintableString(endRow.getBytes(), 0,
+ endRow.getLength(), endRow.getLength())));
+ return 0;
+ }
+ if (size > 0) {
+ shellState.getAccumuloClient().tableOperations().merge(tableName,
startRow, endRow);
+ }
+ return 0;
+ }
+ }
+
@Test
public void testBeginRowHelp() {
assertTrue(
@@ -31,4 +80,68 @@ public class MergeCommandTest {
"-b should say it is exclusive");
}
+ @Test
+ public void mockDryRunMergeTest() throws Exception {
+ MergeCommand cmd = new TestMergeCommand();
+
+ AccumuloClient client = createMock(AccumuloClient.class);
+ ClientContext context = createMock(ClientContext.class);
+ TableOperations tableOps = createMock(TableOperations.class);
+ InstanceOperations instOps = createMock(InstanceOperations.class);
+ Shell shellState = createMock(Shell.class);
+ PrintWriter pw = createMock(PrintWriter.class);
+
+ Capture<String> capture = newCapture();
+ pw.println(capture(capture));
+
+ Options opts = cmd.getOptions();
+
+ CommandLineParser parser = new DefaultParser();
+ String[] args = {"-t", "testTable", "-s", "10G", "--dry-run"};
+ CommandLine cli = parser.parse(opts, args);
+
+ expect(shellState.getAccumuloClient()).andReturn(client).anyTimes();
+ expect(shellState.getContext()).andReturn(context).anyTimes();
+ expect(shellState.isVerbose()).andReturn(false).anyTimes();
+ expect(client.tableOperations()).andReturn(tableOps).anyTimes();
+ expect(tableOps.exists("testTable")).andReturn(true).anyTimes();
+ expect(shellState.getWriter()).andReturn(pw);
+
+ replay(client, context, tableOps, instOps, shellState, pw);
+ cmd.execute("merge", cli, shellState);
+ verify(client, context, tableOps, instOps, shellState, pw);
+ assertEquals(
+ "dry-run would have started a Fate Merge for table testTable tablet
range (-inf to +inf]",
+ capture.getValue());
+ }
+
+ @Test
+ public void mockMergeAllTabletsTest() throws Exception {
+ MergeCommand cmd = new TestMergeCommand();
+
+ AccumuloClient client = createMock(AccumuloClient.class);
+ ClientContext context = createMock(ClientContext.class);
+ TableOperations tableOps = createMock(TableOperations.class);
+ InstanceOperations instOps = createMock(InstanceOperations.class);
+ Shell shellState = createMock(Shell.class);
+
+ Options opts = cmd.getOptions();
+
+ CommandLineParser parser = new DefaultParser();
+ String[] args = {"-t", "testTable"};
+ CommandLine cli = parser.parse(opts, args);
+
+ expect(shellState.getAccumuloClient()).andReturn(client).anyTimes();
+ expect(shellState.getContext()).andReturn(context).anyTimes();
+ expect(shellState.isVerbose()).andReturn(false).anyTimes();
+ expect(client.tableOperations()).andReturn(tableOps).anyTimes();
+ expect(tableOps.exists("testTable")).andReturn(true).anyTimes();
+ expect(shellState.confirm(
+ " Warning!!! Are you REALLY sure you want to merge the entire table {
testTable } into one tablet?!?!?!"))
+ .andReturn(Optional.of(true)).once();
+
+ replay(client, context, tableOps, instOps, shellState);
+ cmd.execute("merge", cli, shellState);
+ verify(client, context, tableOps, instOps, shellState);
+ }
}
diff --git
a/test/src/main/java/org/apache/accumulo/test/functional/MergeIT.java
b/test/src/main/java/org/apache/accumulo/test/functional/MergeIT.java
index 43590e8030..37a057e282 100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/MergeIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/MergeIT.java
@@ -21,9 +21,12 @@ package org.apache.accumulo.test.functional;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -45,6 +48,8 @@ import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
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.TabletFile;
import org.apache.accumulo.core.metadata.schema.ExternalCompactionId;
@@ -109,10 +114,10 @@ public class MergeIT extends AccumuloClusterHarness {
}
c.tableOperations().flush(tableName, null, null, true);
Merge merge = new Merge();
- merge.mergomatic(c, tableName, null, null, 100, false);
+ merge.mergomatic(c, tableName, null, null, 100, false, false);
assertArrayEquals("b c d e f x y".split(" "),
toStrings(c.tableOperations().listSplits(tableName)));
- merge.mergomatic(c, tableName, null, null, 100, true);
+ merge.mergomatic(c, tableName, null, null, 100, true, false);
assertArrayEquals("c e f y".split(" "),
toStrings(c.tableOperations().listSplits(tableName)));
}
}
@@ -278,4 +283,70 @@ public class MergeIT extends AccumuloClusterHarness {
}
}
}
+
+ @Test
+ public void testDryRunMerge() throws Exception {
+ try (AccumuloClient c =
Accumulo.newClient().from(getClientProps()).build()) {
+ String tableName = getUniqueNames(1)[0];
+ c.tableOperations().create(tableName);
+ c.tableOperations().addSplits(tableName,
+ new TreeSet<>(List.of(new Text("row0"), new Text("row1"), new
Text("row10"))));
+ try (BatchWriter writer = c.createBatchWriter(tableName)) {
+ for (int i = 0; i < 100; i++) {
+ Mutation m = new Mutation(String.format("row00%d", i));
+ m.put("cf", "cq", "Test Value");
+ writer.addMutation(m);
+ }
+ }
+ c.tableOperations().flush(tableName, null, null, true);
+
+ TableId tableId = getServerContext().getTableId(tableName);
+ try (var tablets =
getServerContext().getAmple().readTablets().forTable(tableId).build()) {
+ assertEquals(4, tablets.stream().count());
+ }
+
+ List<String> args = new ArrayList<>(List.of("-t", tableName,
"--dry-run", "-s", "1G"));
+ getClientProps().stringPropertyNames().forEach(keyProp -> {
+ args.add("-o");
+ args.add(keyProp + "=" + getClientProps().getProperty(keyProp));
+ });
+ Merge.main(args.toArray(String[]::new));
+ try (var tablets =
getServerContext().getAmple().readTablets().forTable(tableId).build()) {
+ assertEquals(4, tablets.stream().count());
+ }
+ }
+ }
+
+ @Test
+ public void testMetadataTableDoesNotMerge() {
+ List<String> args = new ArrayList<>(List.of("-t", MetadataTable.NAME,
"-e", "~", "-s", "1"));
+ getClientProps().stringPropertyNames().forEach(keyProp -> {
+ args.add("-o");
+ args.add(keyProp + "=" + getClientProps().getProperty(keyProp));
+ });
+ Throwable ex =
+ assertThrows(Merge.MergeException.class, () ->
Merge.main(args.toArray(String[]::new)));
+ assertInstanceOf(IllegalArgumentException.class, ex.getCause());
+ assertEquals(
+ "java.lang.IllegalArgumentException: cannot merge tablets on the
accumulo.metadata table",
+ ex.getMessage());
+ }
+
+ @Test
+ public void testRootTableDoesNotMerge() {
+ List<String> args = new ArrayList<>(List.of("-t", RootTable.NAME));
+ getClientProps().stringPropertyNames().forEach(keyProp -> {
+ args.add("-o");
+ args.add(keyProp + "=" + getClientProps().getProperty(keyProp));
+ });
+ Exception e =
+ assertThrows(Merge.MergeException.class, () ->
Merge.main(args.toArray(String[]::new)));
+ assertInstanceOf(IllegalArgumentException.class, e.getCause());
+ assertEquals(
+ "java.lang.IllegalArgumentException: cannot merge tablets on the
accumulo.root table",
+ e.getMessage());
+ try (var tablets =
getServerContext().getAmple().readTablets().forTable(RootTable.ID).build()) {
+ assertEquals(1, tablets.stream().count());
+ }
+ }
}