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