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

commit f106b8c2122b54d8598fc5595b3132485a4239ba
Merge: b1aa1f58ba fca29a9f85
Author: Keith Turner <ktur...@apache.org>
AuthorDate: Fri Aug 11 18:02:42 2023 -0400

    Merge branch 'main' into elasticity

 .../accumulo/core/classloader/ClassLoaderUtil.java |  19 ++
 .../core/conf/ConfigurationTypeHelper.java         |   2 +-
 .../core/spi/common/ContextClassLoaderFactory.java |   4 +-
 .../accumulo/server/conf/TableConfiguration.java   |   8 +
 .../org/apache/accumulo/server/util/PropUtil.java  |  15 +-
 .../accumulo/server/util/SystemPropUtil.java       |  19 +-
 .../tserver/tablet/MinorCompactionTask.java        |   9 +-
 .../org/apache/accumulo/tserver/tablet/Tablet.java |  75 ++++--
 .../accumulo/shell/commands/ConfigCommand.java     |  31 ++-
 .../test/functional/HalfClosedTablet2IT.java       | 121 +++++++++
 .../test/functional/HalfClosedTabletIT.java        | 282 +++++++++++++++++++++
 .../org/apache/accumulo/test/shell/ShellIT.java    |  16 +-
 .../apache/accumulo/test/shell/ShellServerIT.java  |  79 ++++++
 13 files changed, 638 insertions(+), 42 deletions(-)

diff --cc 
test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTablet2IT.java
index 0000000000,187feff222..fa037d35ca
mode 000000,100644..100644
--- 
a/test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTablet2IT.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTablet2IT.java
@@@ -1,0 -1,121 +1,121 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *   https://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.accumulo.test.functional;
+ 
+ import java.io.File;
+ 
+ import org.apache.accumulo.core.client.Accumulo;
+ import org.apache.accumulo.core.conf.Property;
+ 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.spi.fs.DelegatingChooser;
+ import org.apache.accumulo.core.spi.fs.PreferredVolumeChooser;
+ import org.apache.accumulo.harness.MiniClusterConfigurationCallback;
+ import org.apache.accumulo.harness.SharedMiniClusterBase;
+ import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
+ import org.apache.accumulo.test.util.Wait;
+ import org.apache.hadoop.conf.Configuration;
+ import org.apache.hadoop.io.Text;
+ import org.junit.jupiter.api.AfterAll;
+ import org.junit.jupiter.api.BeforeAll;
+ import org.junit.jupiter.api.Test;
+ 
+ // This covers issues like that reported in 
https://github.com/apache/accumulo/issues/3674
+ // where a failing minor compaction leaves the Tablet in a half-closed state 
that prevents it
+ // from unloading and the TServer from shutting down normally.
+ // This test recreates that scenario by setting an invalid context and 
verifies that the
+ // tablet can recover and unload after the context is set correctly.
+ public class HalfClosedTablet2IT extends SharedMiniClusterBase {
+ 
+   public static class HalfClosedTablet2ITConfiguration implements 
MiniClusterConfigurationCallback {
+ 
+     @Override
+     public void configureMiniCluster(MiniAccumuloConfigImpl cfg, 
Configuration coreSite) {
 -      cfg.setNumTservers(1);
++      cfg.getClusterServerConfiguration().setNumDefaultTabletServers(1);
+       cfg.setProperty(Property.GENERAL_VOLUME_CHOOSER, 
DelegatingChooser.class.getName());
+       cfg.setProperty("general.custom.volume.chooser.default",
+           PreferredVolumeChooser.class.getName());
+       cfg.setProperty("general.custom.volume.preferred.default",
+           new File(cfg.getDir().getAbsolutePath(), 
"/accumulo").toURI().toString());
+     }
+   }
+ 
+   @BeforeAll
+   public static void startup() throws Exception {
+     SharedMiniClusterBase.startMiniClusterWithConfig(new 
HalfClosedTablet2ITConfiguration());
+   }
+ 
+   @AfterAll
+   public static void shutdown() throws Exception {
+     SharedMiniClusterBase.stopMiniCluster();
+   }
+ 
+   @Test
+   public void testInvalidContextCausesVolumeChooserFailure() throws Exception 
{
+ 
+     // In this scenario an invalid context causes the VolumeChooser impl to 
not
+     // be loaded, which causes the MinorCompactionTask to fail to create an
+     // output file. This failure previously caused the minor compaction 
thread to die.
+ 
+     String tableName = getUniqueNames(1)[0];
+     try (final var client = 
Accumulo.newClient().from(getClientProps()).build()) {
+ 
+       final var tops = client.tableOperations();
+       tops.create(tableName);
+       TableId tableId = TableId.of(tops.tableIdMap().get(tableName));
+ 
+       try (final var bw = client.createBatchWriter(tableName)) {
+         final var m1 = new Mutation("a");
+         final var m2 = new Mutation("b");
+         m1.put(new Text("cf"), new Text(), new Value());
+         m2.put(new Text("cf"), new Text(), new Value());
+         bw.addMutation(m1);
+         bw.addMutation(m2);
+       }
+ 
+       
HalfClosedTabletIT.setInvalidClassLoaderContextPropertyWithoutValidation(
+           getCluster().getServerContext(), tableId);
+ 
+       // Need to wait for TabletServer to pickup configuration change
+       Thread.sleep(3000);
+ 
+       tops.flush(tableName);
+ 
+       // minc should fail until invalid context is removed, so there should 
be no files
+       FunctionalTestUtils.checkRFiles(client, tableName, 1, 1, 0, 0);
+ 
+       HalfClosedTabletIT.removeInvalidClassLoaderContextProperty(client, 
tableName);
+ 
+       // Minc should have completed successfully
+       Wait.waitFor(() -> HalfClosedTabletIT.tabletHasExpectedRFiles(client, 
tableName, 1, 1, 1, 1),
+           340_000);
+ 
+       // offline the table which will unload the tablets. If the context 
property is not
+       // removed above, then this test will fail because the tablets will not 
be able to be
+       // unloaded
+       tops.offline(tableName);
+ 
+       Wait.waitFor(() -> HalfClosedTabletIT.countHostedTablets(client, 
tableId) == 0L, 340_000);
+ 
+     }
+ 
+   }
+ 
+ }
diff --cc 
test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTabletIT.java
index 0000000000,c06a23da72..4dff68584c
mode 000000,100644..100644
--- 
a/test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTabletIT.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/functional/HalfClosedTabletIT.java
@@@ -1,0 -1,282 +1,282 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *   https://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.accumulo.test.functional;
+ 
+ import static org.junit.jupiter.api.Assertions.assertTrue;
+ 
+ import java.util.EnumSet;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.concurrent.TimeUnit;
+ 
+ import org.apache.accumulo.core.client.Accumulo;
+ 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.BatchWriter;
+ import org.apache.accumulo.core.client.IteratorSetting;
+ import org.apache.accumulo.core.client.admin.CompactionConfig;
+ import org.apache.accumulo.core.clientImpl.ClientContext;
+ import org.apache.accumulo.core.conf.Property;
+ 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.iterators.IteratorUtil.IteratorScope;
+ import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
+ import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
+ import org.apache.accumulo.core.util.UtilWaitThread;
+ 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.accumulo.server.conf.store.TablePropKey;
+ import org.apache.accumulo.test.util.Wait;
+ import org.apache.hadoop.conf.Configuration;
+ import org.apache.hadoop.io.Text;
+ import org.junit.jupiter.api.AfterAll;
+ import org.junit.jupiter.api.BeforeAll;
+ import org.junit.jupiter.api.Test;
+ 
+ import com.google.common.collect.Sets;
+ 
+ // This IT tests the cases seen in 
https://github.com/apache/accumulo/issues/3674
+ // where a failing minor compaction causes a Tablet.initiateClose to leave the
+ // Tablet in a half-closed state. The half-closed Tablet cannot be unloaded 
and
+ // the TabletServer cannot be shutdown normally. Because the minor compaction 
has
+ // been failing the Tablet needs to be recovered when it's ultimately 
re-assigned.
+ //
+ public class HalfClosedTabletIT extends SharedMiniClusterBase {
+ 
+   public static class HalfClosedTabletITConfiguration implements 
MiniClusterConfigurationCallback {
+ 
+     @Override
+     public void configureMiniCluster(MiniAccumuloConfigImpl cfg, 
Configuration coreSite) {
 -      cfg.setNumTservers(1);
++      cfg.getClusterServerConfiguration().setNumDefaultTabletServers(1);
+     }
+ 
+   }
+ 
+   @BeforeAll
+   public static void startup() throws Exception {
+     SharedMiniClusterBase.startMiniClusterWithConfig(new 
HalfClosedTabletITConfiguration());
+   }
+ 
+   @AfterAll
+   public static void shutdown() throws Exception {
+     SharedMiniClusterBase.stopMiniCluster();
+   }
+ 
+   @Test
+   public void testSplitWithInvalidContext() throws Exception {
+ 
+     // In this scenario the table has been mis-configured with an invalid 
context name.
+     // The minor compaction task is failing because classes like the volume 
chooser or
+     // user iterators cannot be loaded. The user calls Tablet.split which 
calls initiateClose.
+     // This test ensures that the Tablet can still be unloaded normally by 
taking if offline
+     // after the split call with an invalid context. The context property is 
removed after the
+     // split call below to get the minor compaction task to succeed on a 
subsequent run. Because
+     // the minor compaction task backs off when retrying, this could take 
some time.
+ 
+     String tableName = getUniqueNames(1)[0];
+ 
+     try (final var client = 
Accumulo.newClient().from(getClientProps()).build()) {
+       final var tops = client.tableOperations();
+       tops.create(tableName);
+       TableId tableId = TableId.of(tops.tableIdMap().get(tableName));
+       try (final var bw = client.createBatchWriter(tableName)) {
+         final var m1 = new Mutation("a");
+         final var m2 = new Mutation("b");
+         m1.put(new Text("cf"), new Text(), new Value());
+         m2.put(new Text("cf"), new Text(), new Value());
+         bw.addMutation(m1);
+         bw.addMutation(m2);
+       }
+ 
+       
setInvalidClassLoaderContextPropertyWithoutValidation(getCluster().getServerContext(),
+           tableId);
+ 
+       // Need to wait for TabletServer to pickup configuration change
+       Thread.sleep(3000);
+ 
+       Thread configFixer = new Thread(() -> {
+         UtilWaitThread.sleep(3000);
+         removeInvalidClassLoaderContextProperty(client, tableName);
+       });
+ 
+       long t1 = System.nanoTime();
+       configFixer.start();
+ 
+       // The split will probably start running w/ bad config that will cause 
it to get stuck.
+       // However once the config is fixed by the background thread it should 
continue.
+       tops.addSplits(tableName, Sets.newTreeSet(List.of(new Text("b"))));
+ 
+       long t2 = System.nanoTime();
+       // expect that split took at least 3 seconds because that is the time 
it takes to fix the
+       // config
+       assertTrue(TimeUnit.NANOSECONDS.toMillis(t2 - t1) >= 3000);
+ 
+       // offline the table which will unload the tablets. If the context 
property is not
+       // removed above, then this test will fail because the tablets will not 
be able to be
+       // unloaded
+       tops.offline(tableName);
+ 
+       Wait.waitFor(() -> countHostedTablets(client, tableId) == 0L, 340_000);
+     }
+   }
+ 
+   @Test
+   public void testIteratorThrowingTransientError() throws Exception {
+ 
+     // In this scenario a minc iterator throws an error some number of time, 
then
+     // succeeds. We want to verify that the minc is being retried and the 
tablet
+     // can be closed.
+ 
+     try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
+ 
+       String tableName = getUniqueNames(1)[0];
+       final var tops = c.tableOperations();
+ 
+       tops.create(tableName);
+       final var tid = TableId.of(tops.tableIdMap().get(tableName));
+ 
+       try (BatchWriter bw = c.createBatchWriter(tableName)) {
+         Mutation m = new Mutation(new Text("r1"));
+         m.put("acf", tableName, "1");
+         bw.addMutation(m);
+       }
+ 
+       IteratorSetting setting = new IteratorSetting(50, "error", 
ErrorThrowingIterator.class);
+       setting.addOption(ErrorThrowingIterator.TIMES, "3");
+       c.tableOperations().attachIterator(tableName, setting, 
EnumSet.of(IteratorScope.minc));
+       c.tableOperations().compact(tableName, new 
CompactionConfig().setWait(true).setFlush(true));
+ 
+       // Taking the table offline should succeed normally
+       tops.offline(tableName);
+ 
+       // Minc should have completed successfully
+       Wait.waitFor(() -> tabletHasExpectedRFiles(c, tableName, 1, 1, 1, 1), 
340_000);
+ 
+       Wait.waitFor(() -> countHostedTablets(c, tid) == 0L, 340_000);
+ 
+     }
+   }
+ 
+   // Note that these tests can talk several minutes each because by the time 
the test
+   // code changes the configuration, the minc has failed so many times that 
the minc
+   // is waiting for a few minutes before trying again. For example, I saw 
this backoff
+   // timing:
+   //
+   // DEBUG: MinC failed sleeping 169 ms before retrying
+   // DEBUG: MinC failed sleeping 601 ms before retrying
+   // DEBUG: MinC failed sleeping 2004 ms before retrying
+   // DEBUG: MinC failed sleeping 11891 ms before retrying
+   // DEBUG: MinC failed sleeping 43156 ms before retrying
+   // DEBUG: MinC failed sleeping 179779 ms before retrying
+   @Test
+   public void testBadIteratorOnStack() throws Exception {
+ 
+     // In this scenario the table is using an iterator for minc that is 
throwing an exception.
+     // This test ensures that the Tablet can still be unloaded normally by 
taking if offline
+     // after the bad iterator has been removed from the minc configuration.
+ 
+     try (AccumuloClient c = 
Accumulo.newClient().from(getClientProps()).build()) {
+ 
+       String tableName = getUniqueNames(1)[0];
+       final var tops = c.tableOperations();
+ 
+       tops.create(tableName);
+       final var tid = TableId.of(tops.tableIdMap().get(tableName));
+ 
+       IteratorSetting is = new IteratorSetting(30, BadIterator.class);
+       c.tableOperations().attachIterator(tableName, is, 
EnumSet.of(IteratorScope.minc));
+ 
+       try (BatchWriter bw = c.createBatchWriter(tableName)) {
+         Mutation m = new Mutation(new Text("r1"));
+         m.put("acf", tableName, "1");
+         bw.addMutation(m);
+       }
+ 
+       c.tableOperations().flush(tableName, null, null, false);
+ 
+       UtilWaitThread.sleep(5000);
+ 
+       // minc should fail, so there should be no files
+       FunctionalTestUtils.checkRFiles(c, tableName, 1, 1, 0, 0);
+ 
+       // tell the server to take the table offline
+       tops.offline(tableName);
+ 
+       // The offine operation should not be able to complete because the 
tablet can not minor
+       // compact, give the offline operation a bit of time to attempt to 
complete even though it
+       // should never be able to complete.
+       UtilWaitThread.sleep(5000);
+ 
+       assertTrue(countHostedTablets(c, tid) > 0);
+ 
+       // minc should fail, so there should be no files
+       FunctionalTestUtils.checkRFiles(c, tableName, 1, 1, 0, 0);
+ 
+       // remove the bad iterator. The failing minc task is in a backoff retry 
loop
+       // and should pick up this change on the next try
+       c.tableOperations().removeIterator(tableName, 
BadIterator.class.getSimpleName(),
+           EnumSet.of(IteratorScope.minc));
+ 
+       // Minc should have completed successfully
+       Wait.waitFor(() -> tabletHasExpectedRFiles(c, tableName, 1, 1, 1, 1), 
340_000);
+ 
+       // The previous operation to offline the table should be able to 
succeed after the minor
+       // compaction completed
+       Wait.waitFor(() -> countHostedTablets(c, tid) == 0L, 340_000);
+     }
+   }
+ 
+   public static void 
setInvalidClassLoaderContextPropertyWithoutValidation(ServerContext context,
+       TableId tableId) {
+     TablePropKey key = TablePropKey.of(context, tableId);
+     context.getPropStore().putAll(key,
+         Map.of(Property.TABLE_CLASSLOADER_CONTEXT.getKey(), "invalid"));
+   }
+ 
+   public static void removeInvalidClassLoaderContextProperty(AccumuloClient 
client,
+       String tableName) {
+     try {
+       client.tableOperations().removeProperty(tableName,
+           Property.TABLE_CLASSLOADER_CONTEXT.getKey());
+     } catch (AccumuloException | AccumuloSecurityException e) {
+       throw new RuntimeException(e);
+     }
+   }
+ 
+   public static boolean tabletHasExpectedRFiles(AccumuloClient c, String 
tableName, int minTablets,
+       int maxTablets, int minRFiles, int maxRFiles) {
+     try {
+       FunctionalTestUtils.checkRFiles(c, tableName, minTablets, maxTablets, 
minRFiles, maxRFiles);
+       return true;
+     } catch (Exception e) {
+       return false;
+     }
+   }
+ 
+   public static long countHostedTablets(AccumuloClient c, TableId tid) {
+     try (TabletsMetadata tm = ((ClientContext) 
c).getAmple().readTablets().forTable(tid)
+         .fetch(ColumnType.LOCATION).build()) {
+       return tm.stream().count();
+     }
+   }
+ }
diff --cc test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
index 995cdf97df,b460d2eb03..e5177d8f78
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
@@@ -54,12 -50,9 +54,13 @@@ import java.util.regex.Pattern
  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.AccumuloException;
 +import org.apache.accumulo.core.client.BatchScanner;
  import org.apache.accumulo.core.client.IteratorSetting;
 +import org.apache.accumulo.core.client.IteratorSetting.Column;
  import org.apache.accumulo.core.client.Scanner;
 +import org.apache.accumulo.core.client.TableNotFoundException;
 +import org.apache.accumulo.core.client.admin.TabletHostingGoal;
  import org.apache.accumulo.core.client.sample.RowColumnSampler;
  import org.apache.accumulo.core.client.sample.RowSampler;
  import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
@@@ -2259,95 -2090,82 +2260,173 @@@ public class ShellServerIT extends Shar
      assertMatches(output, 
"(?sm).*^.*total[:]2[,]\\s+missing[:]0[,]\\s+extra[:]0.*$.*");
    }
  
 +  // This test serves to verify the listtablets command as well as the 
getTabletInformation api,
 +  // which is used by listtablets.
 +  @Test
 +  public void testListTablets() throws IOException, InterruptedException {
 +
 +    final var tables = getUniqueNames(2);
 +    final String table1 = tables[0];
 +    final String table2 = tables[1];
 +
 +    ts.exec("createtable " + table1, true);
 +    ts.exec("addsplits g n u", true);
 +    ts.exec("setgoal -g always -r g", true);
 +    ts.exec("setgoal -g always -r u", true);
 +    insertData(table1, 1000, 3);
 +    ts.exec("compact -w -t " + table1);
 +    ts.exec("scan -t " + table1);
 +
 +    ts.exec("createtable " + table2, true);
 +    ts.exec("addsplits f m t", true);
 +    ts.exec("setgoal -g always -r n", true);
 +    insertData(table2, 500, 5);
 +    ts.exec("compact -t " + table2);
 +    ts.exec("scan -t " + table1);
 +    ts.exec("setgoal -r g -t " + table2 + " -g NEVER");
 +
 +    // give tablet time to become unassigned
 +    for (var i = 0; i < 15; i++) {
 +      Thread.sleep(1000);
 +      String goal = ts.exec("listtablets -t " + table2, true, "m              
      NEVER");
 +      if (goal.contains("UNASSIGNED None")) {
 +        break;
 +      }
 +    }
 +
 +    String results = ts.exec("listtablets -np -p 
ShellServerIT_testListTablets.", true);
 +    assertTrue(results.contains("TABLE: ShellServerIT_testListTablets0"));
 +    assertTrue(results.contains("TABLE: ShellServerIT_testListTablets1"));
 +    assertTrue(results.contains("1     -INF                 g                 
   ALWAYS"));
 +    assertTrue(results.contains("1     g                    n                 
   ONDEMAND"));
 +    assertTrue(results.contains("1     n                    u                 
   ALWAYS"));
 +    assertTrue(results.contains("1     u                    +INF              
   ONDEMAND"));
 +    assertTrue(results.contains("2     -INF                 f                 
   ONDEMAND"));
 +    assertTrue(results.contains("2     f                    m                 
   NEVER"));
 +    assertTrue(results.contains("2     m                    t                 
   ALWAYS"));
 +    assertTrue(results.contains("2     t                    +INF              
   ONDEMAND"));
 +
 +    // verify the sum of the tablets sizes, number of entries, and dir name 
match the data in a
 +    // metadata scan
 +    String metadata = ts.exec("scan -np -t accumulo.metadata -b 1 -c 
loc,file");
 +    for (String line : metadata.split("\n")) {
 +      String[] tokens = line.split("\\s+");
 +      if (tokens[1].startsWith("loc")) {
 +        String loc = tokens[3];
 +        assertTrue(results.contains(loc));
 +      }
 +      if (tokens[1].startsWith("file")) {
 +        String[] parts = tokens[1].split("/");
 +        String dir = parts[parts.length - 2];
 +        assertTrue(results.contains(dir));
 +        String[] sizes = tokens[3].split(",");
 +        String size = String.format("%,d", Integer.parseInt(sizes[0]));
 +        String entries = String.format("%,d", Integer.parseInt(sizes[1]));
 +        assertTrue(results.contains(size));
 +        assertTrue(results.contains(entries));
 +      }
 +    }
 +  }
 +
 +  private void insertData(String table, int numEntries, int rowLen) throws 
IOException {
 +    for (var i = 0; i < numEntries; i++) {
 +      String alphabet = "abcdefghijklmnopqrstuvwxyz";
 +      String row = String.valueOf(alphabet.charAt(i % 26)) + i;
 +      var cf = "cf" + i;
 +      var cq = "cq" + i;
 +      var data = "asdfqwerty";
 +      ts.exec("insert -t " + table + " " + row + " " + cf + " " + cq + " " + 
data, true);
 +    }
 +  }
 +
 +  private java.nio.file.Path createSplitsFile(final String splitsFile, final 
SortedSet<Text> splits)
 +      throws IOException {
 +    String fullSplitsFile = System.getProperty("user.dir") + "/target/" + 
splitsFile;
 +    java.nio.file.Path path = Paths.get(fullSplitsFile);
 +    try (BufferedWriter writer = Files.newBufferedWriter(path, UTF_8)) {
 +      for (Text text : splits) {
 +        writer.write(text.toString() + '\n');
 +      }
 +    }
 +    return path;
 +  }
 +
+   @Test
+   public void testFateCommandWithSlowCompaction() throws Exception {
+     final String table = getUniqueNames(1)[0];
+ 
+     String orgProps = System.getProperty("accumulo.properties");
+ 
+     System.setProperty("accumulo.properties",
+         "file://" + 
getCluster().getConfig().getAccumuloPropsFile().getCanonicalPath());
+     // compact
+     ts.exec("createtable " + table);
+ 
+     // setup SlowIterator to sleep for 10 seconds
+     ts.exec("config -t " + table
+         + " -s 
table.iterator.majc.slow=1,org.apache.accumulo.test.functional.SlowIterator");
+     ts.exec("config -t " + table + " -s 
table.iterator.majc.slow.opt.sleepTime=10000");
+ 
+     // make two files
+     ts.exec("insert a1 b c v_a1");
+     ts.exec("insert a2 b c v_a2");
+     ts.exec("flush -w");
+     ts.exec("insert x1 b c v_x1");
+     ts.exec("insert x2 b c v_x2");
+     ts.exec("flush -w");
+ 
+     // no transactions running
+     ts.exec("fate -print", true, "0 transactions", true);
+ 
+     // merge two files into one
+     ts.exec("compact -t " + table);
+     Thread.sleep(1_000);
+     // start 2nd transaction
+     ts.exec("compact -t " + table);
+     Thread.sleep(3_000);
+ 
+     // 2 compactions should be running so parse the output to get one of the 
transaction ids
+     log.info("Calling fate print for table = {}", table);
+     String result = ts.exec("fate -print", true, "txid:", true);
+     String[] resultParts = result.split("txid: ");
+     String[] parts = resultParts[1].split(" ");
+     String txid = parts[0];
+     // test filters
+     ts.exec("fate -print -t IN_PROGRESS", true, "2 transactions", true);
+     ts.exec("fate -print " + txid + " -t IN_PROGRESS", true, "1 
transactions", true);
+     ts.exec("fate -print " + txid + " -t FAILED", true, "0 transactions", 
true);
+     ts.exec("fate -print -t NEW", true, "0 transactions", true);
+     ts.exec("fate -print 1234", true, "0 transactions", true);
+     ts.exec("fate -print FATE[aaa] 1 2 3", true, "0 transactions", true);
+ 
+     ts.exec("deletetable -f " + table);
+ 
+     if (orgProps != null) {
+       System.setProperty("accumulo.properties", orgProps);
+     }
+   }
+ 
+   @Test
+   public void failOnInvalidClassloaderContestTest() throws Exception {
+ 
+     final String[] names = getUniqueNames(3);
+     final String table1 = names[0];
+     final String namespace1 = names[1];
+     final String table2 = namespace1 + "." + names[2];
+ 
+     ts.exec("createtable " + table1, true);
+     ts.exec("createnamespace " + namespace1, true);
+     ts.exec("createtable " + table2, true);
+ 
+     ts.exec("config -s table.class.loader.context=invalid", false,
+         AccumuloException.class.getName(), true);
+     ts.exec("config -s table.class.loader.context=invalid -ns " + namespace1, 
false,
+         AccumuloException.class.getName(), true);
+     ts.exec("config -s table.class.loader.context=invalid -t " + table1, 
false,
+         AccumuloException.class.getName(), true);
+     ts.exec("config -s table.class.loader.context=invalid -t " + table2, 
false,
+         AccumuloException.class.getName(), true);
+ 
+   }
+ 
  }

Reply via email to