| /* |
| * 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 |
| * |
| * http://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; |
| |
| import static java.nio.file.Files.newBufferedReader; |
| import static org.apache.accumulo.fate.util.UtilWaitThread.sleepUninterruptibly; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Constructor; |
| import java.nio.charset.Charset; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.security.SecureRandom; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Base64; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| |
| 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.AccumuloSecurityException; |
| import org.apache.accumulo.core.client.IteratorSetting; |
| import org.apache.accumulo.core.client.Scanner; |
| import org.apache.accumulo.core.client.TableNotFoundException; |
| import org.apache.accumulo.core.client.admin.TableOperations; |
| import org.apache.accumulo.core.client.sample.RowSampler; |
| import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; |
| import org.apache.accumulo.core.client.security.tokens.KerberosToken; |
| import org.apache.accumulo.core.client.security.tokens.PasswordToken; |
| import org.apache.accumulo.core.client.summary.summarizers.DeletesSummarizer; |
| import org.apache.accumulo.core.client.summary.summarizers.FamilySummarizer; |
| import org.apache.accumulo.core.clientImpl.ClientInfo; |
| import org.apache.accumulo.core.clientImpl.Namespace; |
| import org.apache.accumulo.core.conf.AccumuloConfiguration; |
| import org.apache.accumulo.core.conf.DefaultConfiguration; |
| import org.apache.accumulo.core.conf.Property; |
| import org.apache.accumulo.core.crypto.CryptoServiceFactory; |
| import org.apache.accumulo.core.data.Key; |
| import org.apache.accumulo.core.data.Value; |
| import org.apache.accumulo.core.file.FileOperations; |
| import org.apache.accumulo.core.file.FileSKVWriter; |
| import org.apache.accumulo.core.metadata.MetadataTable; |
| import org.apache.accumulo.core.security.Authorizations; |
| import org.apache.accumulo.core.util.TextUtil; |
| import org.apache.accumulo.core.util.format.Formatter; |
| import org.apache.accumulo.core.util.format.FormatterConfig; |
| import org.apache.accumulo.harness.MiniClusterConfigurationCallback; |
| import org.apache.accumulo.harness.SharedMiniClusterBase; |
| import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl; |
| import org.apache.accumulo.shell.Shell; |
| import org.apache.accumulo.test.categories.MiniClusterOnlyTests; |
| import org.apache.accumulo.test.categories.SunnyDayTests; |
| import org.apache.accumulo.test.functional.SlowIterator; |
| import org.apache.accumulo.tracer.TraceServer; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.io.Text; |
| import org.apache.hadoop.tools.DistCp; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.junit.rules.TestName; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Iterators; |
| |
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| import jline.console.ConsoleReader; |
| |
| @Category({MiniClusterOnlyTests.class, SunnyDayTests.class}) |
| public class ShellServerIT extends SharedMiniClusterBase { |
| public static class TestOutputStream extends OutputStream { |
| StringBuilder sb = new StringBuilder(); |
| |
| @Override |
| public void write(int b) { |
| sb.append((char) (0xff & b)); |
| } |
| |
| public String get() { |
| return sb.toString(); |
| } |
| |
| public void clear() { |
| sb.setLength(0); |
| } |
| } |
| |
| private static final Logger log = LoggerFactory.getLogger(ShellServerIT.class); |
| |
| public static class StringInputStream extends InputStream { |
| private String source = ""; |
| private int offset = 0; |
| |
| @Override |
| public int read() { |
| if (offset == source.length()) |
| return '\n'; |
| else |
| return source.charAt(offset++); |
| } |
| |
| public void set(String other) { |
| source = other; |
| offset = 0; |
| } |
| } |
| |
| private abstract static class ErrorMessageCallback { |
| public abstract String getErrorMessage(); |
| } |
| |
| private static class NoOpErrorMessageCallback extends ErrorMessageCallback { |
| private static final String empty = ""; |
| |
| @Override |
| public String getErrorMessage() { |
| return empty; |
| } |
| } |
| |
| public static class TestShell { |
| private static final Logger shellLog = LoggerFactory.getLogger(TestShell.class); |
| public TestOutputStream output; |
| public StringInputStream input; |
| public Shell shell; |
| |
| TestShell(String user, String rootPass, String instanceName, String zookeepers, File configFile) |
| throws IOException { |
| ClientInfo info = ClientInfo.from(configFile.toPath()); |
| // start the shell |
| output = new TestOutputStream(); |
| input = new StringInputStream(); |
| shell = new Shell(new ConsoleReader(input, output)); |
| shell.setLogErrorsToConsole(); |
| if (info.saslEnabled()) { |
| // Pull the kerberos principal out when we're using SASL |
| shell.config("-u", user, "-z", instanceName, zookeepers, "--config-file", |
| configFile.getAbsolutePath()); |
| } else { |
| shell.config("-u", user, "-p", rootPass, "-z", instanceName, zookeepers, "--config-file", |
| configFile.getAbsolutePath()); |
| } |
| exec("quit", true); |
| shell.start(); |
| shell.setExit(false); |
| } |
| |
| String exec(String cmd) throws IOException { |
| output.clear(); |
| shell.execCommand(cmd, true, true); |
| return output.get(); |
| } |
| |
| String exec(String cmd, boolean expectGoodExit) throws IOException { |
| return exec(cmd, expectGoodExit, noop); |
| } |
| |
| String exec(String cmd, boolean expectGoodExit, ErrorMessageCallback callback) |
| throws IOException { |
| String result = exec(cmd); |
| if (expectGoodExit) |
| assertGoodExit("", true, callback); |
| else |
| assertBadExit("", true, callback); |
| return result; |
| } |
| |
| String exec(String cmd, boolean expectGoodExit, String expectString) throws IOException { |
| return exec(cmd, expectGoodExit, expectString, noop); |
| } |
| |
| String exec(String cmd, boolean expectGoodExit, String expectString, |
| ErrorMessageCallback callback) throws IOException { |
| return exec(cmd, expectGoodExit, expectString, true, callback); |
| } |
| |
| String exec(String cmd, boolean expectGoodExit, String expectString, boolean stringPresent) |
| throws IOException { |
| return exec(cmd, expectGoodExit, expectString, stringPresent, noop); |
| } |
| |
| String exec(String cmd, boolean expectGoodExit, String expectString, boolean stringPresent, |
| ErrorMessageCallback callback) throws IOException { |
| String result = exec(cmd); |
| if (expectGoodExit) |
| assertGoodExit(expectString, stringPresent, callback); |
| else |
| assertBadExit(expectString, stringPresent, callback); |
| return result; |
| } |
| |
| void assertGoodExit(String s, boolean stringPresent) { |
| assertGoodExit(s, stringPresent, noop); |
| } |
| |
| void assertGoodExit(String s, boolean stringPresent, ErrorMessageCallback callback) { |
| shellLog.debug("Shell Output: '{}'", output.get()); |
| if (shell.getExitCode() != 0) { |
| String errorMsg = callback.getErrorMessage(); |
| assertEquals(errorMsg, 0, shell.getExitCode()); |
| } |
| |
| if (s.length() > 0) |
| assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent, |
| output.get().contains(s)); |
| } |
| |
| void assertBadExit(String s, boolean stringPresent, ErrorMessageCallback callback) { |
| shellLog.debug(output.get()); |
| if (shell.getExitCode() == 0) { |
| String errorMsg = callback.getErrorMessage(); |
| assertTrue(errorMsg, shell.getExitCode() > 0); |
| } |
| |
| if (s.length() > 0) |
| assertEquals(s + " present in " + output.get() + " was not " + stringPresent, stringPresent, |
| output.get().contains(s)); |
| shell.resetExitCode(); |
| } |
| } |
| |
| private static final NoOpErrorMessageCallback noop = new NoOpErrorMessageCallback(); |
| |
| private TestShell ts; |
| |
| private static Process traceProcess; |
| private static String rootPath; |
| |
| @Rule |
| public TestName name = new TestName(); |
| |
| private static class ShellServerITConfigCallback implements MiniClusterConfigurationCallback { |
| @Override |
| public void configureMiniCluster(MiniAccumuloConfigImpl cfg, Configuration coreSite) { |
| // Only one tserver to avoid race conditions on ZK propagation (auths and configuration) |
| cfg.setNumTservers(1); |
| // Set the min span to 0 so we will definitely get all the traces back. See ACCUMULO-4365 |
| Map<String,String> siteConf = cfg.getSiteConfig(); |
| siteConf.put(Property.TRACE_SPAN_RECEIVER_PREFIX.getKey() + "tracer.span.min.ms", "0"); |
| cfg.setSiteConfig(siteConf); |
| } |
| } |
| |
| @BeforeClass |
| public static void setupMiniCluster() throws Exception { |
| SharedMiniClusterBase.startMiniClusterWithConfig(new ShellServerITConfigCallback()); |
| rootPath = getMiniClusterDir().getAbsolutePath(); |
| |
| String userDir = System.getProperty("user.dir"); |
| |
| // history file is updated in $HOME |
| System.setProperty("HOME", rootPath); |
| System.setProperty("hadoop.tmp.dir", userDir + "/target/hadoop-tmp"); |
| |
| traceProcess = getCluster().exec(TraceServer.class).getProcess(); |
| |
| Properties props = getCluster().getClientProperties(); |
| try (AccumuloClient client = Accumulo.newClient().from(props).build()) { |
| TableOperations tops = client.tableOperations(); |
| // give the tracer some time to start |
| while (!tops.exists("trace")) { |
| sleepUninterruptibly(1, TimeUnit.SECONDS); |
| } |
| } |
| } |
| |
| @Before |
| public void setupShell() throws Exception { |
| ts = new TestShell(getPrincipal(), getRootPassword(), |
| getCluster().getConfig().getInstanceName(), getCluster().getConfig().getZooKeepers(), |
| getCluster().getConfig().getClientPropsFile()); |
| } |
| |
| @AfterClass |
| public static void tearDownAfterClass() { |
| if (traceProcess != null) { |
| traceProcess.destroy(); |
| } |
| |
| SharedMiniClusterBase.stopMiniCluster(); |
| } |
| |
| @After |
| public void deleteTables() throws Exception { |
| try (AccumuloClient c = Accumulo.newClient().from(getClientProps()).build()) { |
| for (String table : c.tableOperations().list()) { |
| if (!table.startsWith(Namespace.ACCUMULO.name() + ".") && !table.equals("trace")) |
| try { |
| c.tableOperations().delete(table); |
| } catch (TableNotFoundException e) { |
| // don't care |
| } |
| } |
| } |
| } |
| |
| @After |
| public void tearDownShell() { |
| ts.shell.shutdown(); |
| } |
| |
| @Override |
| public int defaultTimeoutSeconds() { |
| return 60; |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "path provided by test") |
| @Test |
| public void exporttableImporttable() throws Exception { |
| final String table = name.getMethodName(), table2 = table + "2"; |
| |
| // exporttable / importtable |
| ts.exec("createtable " + table + " -evc", true); |
| make10(); |
| ts.exec("addsplits row5", true); |
| ts.exec("config -t " + table + " -s table.split.threshold=345M", true); |
| ts.exec("offline " + table, true); |
| File exportDir = new File(rootPath, "ShellServerIT.export"); |
| String exportUri = "file://" + exportDir; |
| String localTmp = "file://" + new File(rootPath, "ShellServerIT.tmp"); |
| ts.exec("exporttable -t " + table + " " + exportUri, true); |
| DistCp cp = newDistCp(new Configuration(false)); |
| String import_ = "file://" + new File(rootPath, "ShellServerIT.import"); |
| ClientInfo info = ClientInfo.from(getCluster().getClientProperties()); |
| if (info.saslEnabled()) { |
| // DistCp bugs out trying to get a fs delegation token to perform the cp. Just copy it |
| // ourselves by hand. |
| FileSystem fs = getCluster().getFileSystem(); |
| FileSystem localFs = FileSystem.getLocal(new Configuration(false)); |
| |
| // Path on local fs to cp into |
| Path localTmpPath = new Path(localTmp); |
| localFs.mkdirs(localTmpPath); |
| |
| // Path in remote fs to importtable from |
| Path importDir = new Path(import_); |
| fs.mkdirs(importDir); |
| |
| // Implement a poor-man's DistCp |
| try (BufferedReader reader = |
| new BufferedReader(new FileReader(new File(exportDir, "distcp.txt")))) { |
| for (String line; (line = reader.readLine()) != null;) { |
| Path exportedFile = new Path(line); |
| // There isn't a cp on FileSystem?? |
| log.info("Copying {} to {}", line, localTmpPath); |
| fs.copyToLocalFile(exportedFile, localTmpPath); |
| Path tmpFile = new Path(localTmpPath, exportedFile.getName()); |
| log.info("Moving {} to the import directory {}", tmpFile, importDir); |
| fs.moveFromLocalFile(tmpFile, importDir); |
| } |
| } |
| } else { |
| String[] distCpArgs = {"-f", exportUri + "/distcp.txt", import_}; |
| assertEquals("Failed to run distcp: " + Arrays.toString(distCpArgs), 0, cp.run(distCpArgs)); |
| } |
| ts.exec("importtable " + table2 + " " + import_, true); |
| ts.exec("config -t " + table2 + " -np", true, "345M", true); |
| ts.exec("getsplits -t " + table2, true, "row5", true); |
| ts.exec("constraint --list -t " + table2, true, "VisibilityConstraint=2", true); |
| ts.exec("online " + table, true); |
| ts.exec("deletetable -f " + table, true); |
| ts.exec("deletetable -f " + table2, true); |
| } |
| |
| private DistCp newDistCp(Configuration conf) { |
| try { |
| @SuppressWarnings("unchecked") |
| Constructor<DistCp>[] constructors = (Constructor<DistCp>[]) DistCp.class.getConstructors(); |
| for (Constructor<DistCp> constructor : constructors) { |
| Class<?>[] parameterTypes = constructor.getParameterTypes(); |
| if (parameterTypes.length > 0 && parameterTypes[0].equals(Configuration.class)) { |
| if (parameterTypes.length == 1) { |
| return constructor.newInstance(conf); |
| } else if (parameterTypes.length == 2) { |
| return constructor.newInstance(conf, null); |
| } |
| } |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| throw new RuntimeException("Unexpected constructors for DistCp"); |
| } |
| |
| @Test |
| public void setscaniterDeletescaniter() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // setscaniter, deletescaniter |
| ts.exec("createtable " + table); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.input.set("true\n\n\n\nSTRING"); |
| ts.exec("setscaniter -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -n name", true); |
| ts.exec("scan", true, "3", true); |
| ts.exec("deletescaniter -n name", true); |
| ts.exec("scan", true, "1", true); |
| ts.exec("deletetable -f " + table); |
| |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "path provided by test") |
| @Test |
| public void execfile() throws Exception { |
| // execfile |
| File file = File.createTempFile("ShellServerIT.execfile", ".conf", new File(rootPath)); |
| PrintWriter writer = new PrintWriter(file.getAbsolutePath()); |
| writer.println("about"); |
| writer.close(); |
| ts.exec("execfile " + file.getAbsolutePath(), true, Constants.VERSION, true); |
| |
| } |
| |
| @Test |
| public void egrep() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // egrep |
| ts.exec("createtable " + table); |
| make10(); |
| String lines = ts.exec("egrep row[123]", true); |
| assertEquals(3, lines.split("\n").length - 1); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void du() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // create and delete a table so we get out of a table context in the shell |
| ts.exec("notable", true); |
| |
| // Calling du not in a table context shouldn't throw an error |
| ts.output.clear(); |
| ts.exec("du", true, "", true); |
| |
| ts.output.clear(); |
| ts.exec("createtable " + table); |
| make10(); |
| ts.exec("flush -t " + table + " -w"); |
| ts.exec("du " + table, true, " [" + table + "]", true); |
| ts.output.clear(); |
| ts.shell.execCommand("du -h", false, false); |
| String o = ts.output.get(); |
| // for some reason, there's a bit of fluctuation |
| assertTrue("Output did not match regex: '" + o + "'", |
| o.matches(".*[1-9][0-9][0-9]\\s\\[" + table + "\\]\\n")); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| /* |
| * This test should be deleted when the debug command is removed |
| */ |
| @Deprecated |
| @Test |
| public void debug() throws Exception { |
| String expectMsg = "The debug command is deprecated"; |
| ts.exec("debug", false, expectMsg); |
| ts.exec("debug on", false, expectMsg); |
| ts.exec("debug", false, expectMsg); |
| ts.exec("debug off", false, expectMsg); |
| ts.exec("debug", false, expectMsg); |
| ts.exec("debug debug", false, expectMsg); |
| ts.exec("debug debug debug", false, expectMsg); |
| } |
| |
| @Test |
| public void user() throws Exception { |
| final String table = name.getMethodName(); |
| final boolean kerberosEnabled = getToken() instanceof KerberosToken; |
| |
| // createuser, deleteuser, user, users, droptable, grant, revoke |
| if (!kerberosEnabled) { |
| ts.input.set("secret\nsecret\n"); |
| } |
| ts.exec("createuser xyzzy", true); |
| ts.exec("users", true, "xyzzy", true); |
| String perms = ts.exec("userpermissions -u xyzzy", true); |
| assertTrue(perms.contains("Table permissions (" + MetadataTable.NAME + "): Table.READ")); |
| ts.exec("grant -u xyzzy -s System.CREATE_TABLE", true); |
| perms = ts.exec("userpermissions -u xyzzy", true); |
| assertTrue(perms.contains("")); |
| ts.exec("grant -u " + getPrincipal() + " -t " + MetadataTable.NAME + " Table.WRITE", true); |
| ts.exec("grant -u " + getPrincipal() + " -t " + MetadataTable.NAME + " Table.GOOFY", false); |
| ts.exec("grant -u " + getPrincipal() + " -s foo", false); |
| ts.exec("grant -u xyzzy -t " + MetadataTable.NAME + " foo", false); |
| if (!kerberosEnabled) { |
| ts.input.set("secret\nsecret\n"); |
| ts.exec("user xyzzy", true); |
| ts.exec("createtable " + table, true, "xyzzy@", true); |
| ts.exec("insert row1 cf cq 1", true); |
| ts.exec("scan", true, "row1", true); |
| ts.exec("droptable -f " + table, true); |
| ts.input.set(getRootPassword() + "\n" + getRootPassword() + "\n"); |
| ts.exec("user root", true); |
| } |
| ts.exec("deleteuser " + getPrincipal(), false, "delete yourself", true); |
| ts.exec("revoke -u xyzzy -s System.CREATE_TABLE", true); |
| ts.exec("revoke -u xyzzy -s System.GOOFY", false); |
| ts.exec("revoke -u xyzzy -s foo", false); |
| ts.exec("revoke -u xyzzy -t " + MetadataTable.NAME + " Table.WRITE", true); |
| ts.exec("revoke -u xyzzy -t " + MetadataTable.NAME + " Table.GOOFY", false); |
| ts.exec("revoke -u xyzzy -t " + MetadataTable.NAME + " foo", false); |
| ts.exec("deleteuser xyzzy", true, "deleteuser { xyzzy } (yes|no)?", true); |
| ts.exec("deleteuser -f xyzzy", true); |
| ts.exec("users", true, "xyzzy", false); |
| } |
| |
| @Test |
| public void durability() throws Exception { |
| final String table = name.getMethodName(); |
| ts.exec("createtable " + table); |
| ts.exec("insert -d none a cf cq randomGunkaASDFWEAQRd"); |
| ts.exec("insert -d foo a cf cq2 2", false, "foo", true); |
| ts.exec("scan -r a", true, "randomGunkaASDFWEAQRd", true); |
| ts.exec("scan -r a", true, "foo", false); |
| } |
| |
| @Test |
| public void iter() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // setshelliter, listshelliter, deleteshelliter |
| ts.exec("createtable " + table); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.input.set("true\n\n\n\nSTRING\n"); |
| ts.exec("setshelliter -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -pn sum -n name", true); |
| ts.exec("setshelliter -class " + SUMMING_COMBINER_ITERATOR + " -p 11 -pn sum -n name", false); |
| ts.exec("setshelliter -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -pn sum -n other", false); |
| ts.input.set("true\n\n\n\nSTRING\n"); |
| ts.exec("setshelliter -class " + SUMMING_COMBINER_ITERATOR + " -p 11 -pn sum -n xyzzy", true); |
| ts.exec("scan -pn sum", true, "3", true); |
| ts.exec("listshelliter", true, "Iterator name", true); |
| ts.exec("listshelliter", true, "Iterator xyzzy", true); |
| ts.exec("listshelliter", true, "Profile : sum", true); |
| ts.exec("deleteshelliter -pn sum -n name", true); |
| ts.exec("listshelliter", true, "Iterator name", false); |
| ts.exec("listshelliter", true, "Iterator xyzzy", true); |
| ts.exec("deleteshelliter -pn sum -a", true); |
| ts.exec("listshelliter", true, "Iterator xyzzy", false); |
| ts.exec("listshelliter", true, "Profile : sum", false); |
| ts.exec("deletetable -f " + table); |
| // list iter |
| ts.exec("createtable " + table); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.exec("insert a cf cq 1"); |
| ts.input.set("true\n\n\n\nSTRING\n"); |
| ts.exec("setiter -scan -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -n name", true); |
| ts.exec("setiter -scan -class " + SUMMING_COMBINER_ITERATOR + " -p 11 -n name", false); |
| ts.exec("setiter -scan -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -n other", false); |
| ts.input.set("true\n\n\n\nSTRING\n"); |
| ts.exec("setiter -scan -class " + SUMMING_COMBINER_ITERATOR + " -p 11 -n xyzzy", true); |
| ts.exec("scan", true, "3", true); |
| ts.exec("listiter -scan", true, "Iterator name", true); |
| ts.exec("listiter -scan", true, "Iterator xyzzy", true); |
| ts.exec("listiter -minc", true, "Iterator name", false); |
| ts.exec("listiter -minc", true, "Iterator xyzzy", false); |
| ts.exec("deleteiter -scan -n name", true); |
| ts.exec("listiter -scan", true, "Iterator name", false); |
| ts.exec("listiter -scan", true, "Iterator xyzzy", true); |
| ts.exec("deletetable -f " + table); |
| |
| } |
| |
| @Test |
| public void setIterOptionPrompt() throws Exception { |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| String tableName = name.getMethodName(); |
| |
| ts.exec("createtable " + tableName); |
| ts.input.set("\n\n"); |
| // Setting a non-optiondescriber with no name should fail |
| ts.exec("setiter -scan -class " + COLUMN_FAMILY_COUNTER_ITERATOR + " -p 30", false); |
| |
| // Name as option will work |
| ts.exec("setiter -scan -class " + COLUMN_FAMILY_COUNTER_ITERATOR + " -p 30 -name cfcounter", |
| true); |
| |
| String expectedKey = "table.iterator.scan.cfcounter"; |
| String expectedValue = "30," + COLUMN_FAMILY_COUNTER_ITERATOR; |
| TableOperations tops = client.tableOperations(); |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| |
| ts.exec("deletetable " + tableName, true); |
| tableName = tableName + "1"; |
| |
| ts.exec("createtable " + tableName, true); |
| |
| ts.input.set("customcfcounter\n\n"); |
| |
| // Name on the CLI should override OptionDescriber (or user input name, in this case) |
| ts.exec("setiter -scan -class " + COLUMN_FAMILY_COUNTER_ITERATOR + " -p 30", true); |
| expectedKey = "table.iterator.scan.customcfcounter"; |
| expectedValue = "30," + COLUMN_FAMILY_COUNTER_ITERATOR; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| |
| ts.exec("deletetable " + tableName, true); |
| tableName = tableName + "1"; |
| |
| ts.exec("createtable " + tableName, true); |
| |
| ts.input.set("customcfcounter\nname1 value1\nname2 value2\n\n"); |
| |
| // Name on the CLI should override OptionDescriber (or user input name, in this case) |
| ts.exec("setiter -scan -class " + COLUMN_FAMILY_COUNTER_ITERATOR + " -p 30", true); |
| expectedKey = "table.iterator.scan.customcfcounter"; |
| expectedValue = "30," + COLUMN_FAMILY_COUNTER_ITERATOR; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| expectedKey = "table.iterator.scan.customcfcounter.opt.name1"; |
| expectedValue = "value1"; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| expectedKey = "table.iterator.scan.customcfcounter.opt.name2"; |
| expectedValue = "value2"; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| |
| ts.exec("deletetable " + tableName, true); |
| tableName = tableName + "1"; |
| |
| ts.exec("createtable " + tableName, true); |
| |
| ts.input.set("\nname1 value1.1,value1.2,value1.3\nname2 value2\n\n"); |
| |
| // Name on the CLI should override OptionDescriber (or user input name, in this case) |
| ts.exec("setiter -scan -class " + COLUMN_FAMILY_COUNTER_ITERATOR + " -p 30 -name cfcounter", |
| true); |
| expectedKey = "table.iterator.scan.cfcounter"; |
| expectedValue = "30," + COLUMN_FAMILY_COUNTER_ITERATOR; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| expectedKey = "table.iterator.scan.cfcounter.opt.name1"; |
| expectedValue = "value1.1,value1.2,value1.3"; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| expectedKey = "table.iterator.scan.cfcounter.opt.name2"; |
| expectedValue = "value2"; |
| checkTableForProperty(tops, tableName, expectedKey, expectedValue); |
| } |
| } |
| |
| protected void checkTableForProperty(TableOperations tops, String tableName, String expectedKey, |
| String expectedValue) throws Exception { |
| for (int i = 0; i < 5; i++) { |
| for (Entry<String,String> entry : tops.getProperties(tableName)) { |
| if (expectedKey.equals(entry.getKey())) { |
| assertEquals(expectedValue, entry.getValue()); |
| return; |
| } |
| } |
| Thread.sleep(500); |
| } |
| |
| fail("Failed to find expected property on " + tableName + ": " + expectedKey + "=" |
| + expectedValue); |
| } |
| |
| @Test |
| public void notable() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // notable |
| ts.exec("createtable " + table, true); |
| ts.exec("scan", true, " " + table + ">", true); |
| assertTrue(ts.output.get().contains(" " + table + ">")); |
| ts.exec("notable", true); |
| ts.exec("scan", false, "Not in a table context.", true); |
| assertFalse(ts.output.get().contains(" " + table + ">")); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void sleep() throws Exception { |
| // sleep |
| long now = System.currentTimeMillis(); |
| ts.exec("sleep 0.2", true); |
| long diff = System.currentTimeMillis() - now; |
| assertTrue("Diff was actually " + diff, diff >= 200); |
| assertTrue("Diff was actually " + diff, diff < 600); |
| } |
| |
| @Test |
| public void addauths() throws Exception { |
| final String table = name.getMethodName(); |
| // addauths |
| ts.exec("createtable " + table + " -evc"); |
| boolean success = false; |
| // Rely on the timeout rule in AccumuloIT |
| while (!success) { |
| try { |
| ts.exec("insert a b c d -l foo", false, "does not have authorization", true, |
| new ErrorMessageCallback() { |
| @Override |
| public String getErrorMessage() { |
| try (AccumuloClient c = Accumulo.newClient().from(getClientProps()).build()) { |
| return "Current auths for root are: " |
| + c.securityOperations().getUserAuthorizations("root"); |
| } catch (Exception e) { |
| return "Could not check authorizations"; |
| } |
| } |
| }); |
| success = true; |
| } catch (AssertionError e) { |
| Thread.sleep(500); |
| } |
| } |
| ts.exec("addauths -s foo,bar", true); |
| boolean passed = false; |
| // Rely on the timeout rule in AccumuloIT |
| while (!passed) { |
| try { |
| ts.exec("getauths", true, "foo", true); |
| ts.exec("getauths", true, "bar", true); |
| passed = true; |
| } catch (AssertionError | Exception e) { |
| sleepUninterruptibly(500, TimeUnit.MILLISECONDS); |
| } |
| } |
| assertTrue("Could not successfully see updated authoriations", passed); |
| ts.exec("insert a b c d -l foo"); |
| ts.exec("scan", true, "[foo]"); |
| ts.exec("scan -s bar", true, "[foo]", false); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void getAuths() throws Exception { |
| Assume.assumeFalse("test skipped for kerberos", getToken() instanceof KerberosToken); |
| |
| // create two users with different auths |
| for (int i = 1; i <= 2; i++) { |
| String userName = name.getMethodName() + "user" + i; |
| String password = "password" + i; |
| String auths = "auth" + i + "A,auth" + i + "B"; |
| ts.exec("createuser " + userName, true); |
| ts.exec(password, true); |
| ts.exec("addauths -u " + userName + " -s " + auths, true); |
| } |
| |
| // get auths using root user, which has System.SYSTEM |
| ts.exec("getauths -u getAuthsuser1", true, "auth1A", true); |
| ts.exec("getauths -u getAuthsuser1", true, "auth1B", true); |
| ts.exec("getauths -u getAuthsuser2", true, "auth2A", true); |
| ts.exec("getauths -u getAuthsuser2", true, "auth2B", true); |
| |
| // grant the first user the ability to see other users auths |
| ts.exec("grant -u getAuthsuser1 -s System.ALTER_USER", true); |
| |
| // switch to first user (the one with the ALTER_USER perm) |
| ts.exec("user getAuthsuser1", true); |
| ts.exec("password1", true); |
| |
| // get auths for self and other user |
| ts.exec("getauths -u getAuthsuser1", true, "auth1A", true); |
| ts.exec("getauths -u getAuthsuser1", true, "auth1B", true); |
| ts.exec("getauths -u getAuthsuser2", true, "auth2A", true); |
| ts.exec("getauths -u getAuthsuser2", true, "auth2B", true); |
| |
| // switch to second user (the one without the ALTER_USER perm) |
| ts.exec("user getAuthsuser2", true); |
| ts.exec("password2", true); |
| |
| // get auths for self, but not other user |
| ts.exec("getauths -u getAuthsuser2", true, "auth2A", true); |
| ts.exec("getauths -u getAuthsuser2", true, "auth2B", true); |
| ts.exec("getauths -u getAuthsuser1", false, "PERMISSION_DENIED", true); |
| ts.exec("getauths -u getAuthsuser1", false, "PERMISSION_DENIED", true); |
| } |
| |
| @Test |
| public void byeQuitExit() throws Exception { |
| // bye, quit, exit |
| for (String cmd : "bye quit exit".split(" ")) { |
| assertFalse(ts.shell.getExit()); |
| ts.exec(cmd); |
| assertTrue(ts.shell.getExit()); |
| ts.shell.setExit(false); |
| } |
| } |
| |
| @Test |
| public void classpath() throws Exception { |
| // classpath |
| ts.exec("classpath", true, |
| "Level 2: Java Classloader (loads everything" + " defined by java classpath)", true); |
| } |
| |
| @Test |
| public void clearCls() throws Exception { |
| // clear/cls |
| if (ts.shell.getReader().getTerminal().isAnsiSupported()) { |
| ts.exec("cls", true, "[1;1H"); |
| ts.exec("clear", true, "[2J"); |
| } else { |
| ts.exec("cls", false, "does not support"); |
| ts.exec("clear", false, "does not support"); |
| } |
| } |
| |
| @Test |
| public void clonetable() throws Exception { |
| final String table = name.getMethodName(), clone = table + "_clone"; |
| |
| // clonetable |
| ts.exec("createtable " + table + " -evc"); |
| ts.exec("config -t " + table + " -s table.split.threshold=123M", true); |
| ts.exec("addsplits -t " + table + " a b c", true); |
| ts.exec("insert a b c value"); |
| ts.exec("scan", true, "value", true); |
| ts.exec("clonetable " + table + " " + clone); |
| // verify constraint, config, and splits were cloned |
| ts.exec("constraint --list -t " + clone, true, "VisibilityConstraint=2", true); |
| ts.exec("config -t " + clone + " -np", true, "123M", true); |
| ts.exec("getsplits -t " + clone, true, "a\nb\nc\n"); |
| ts.exec("deletetable -f " + table); |
| ts.exec("deletetable -f " + clone); |
| } |
| |
| @Test |
| public void createTableWithProperties() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // create table with initial properties |
| String testProp = "table.custom.description=description,table.custom.testProp=testProp," |
| + Property.TABLE_SPLIT_THRESHOLD.getKey() + "=10K"; |
| |
| ts.exec("createtable " + table + " -prop " + testProp, true); |
| ts.exec("insert a b c value", true); |
| ts.exec("scan", true, "value", true); |
| |
| try (AccumuloClient accumuloClient = Accumulo.newClient().from(getClientProps()).build()) { |
| for (Entry<String,String> entry : accumuloClient.tableOperations().getProperties(table)) { |
| if (entry.getKey().equals("table.custom.description")) |
| assertEquals("Initial property was not set correctly", "description", entry.getValue()); |
| |
| if (entry.getKey().equals("table.custom.testProp")) |
| assertEquals("Initial property was not set correctly", "testProp", entry.getValue()); |
| |
| if (entry.getKey().equals(Property.TABLE_SPLIT_THRESHOLD.getKey())) |
| assertEquals("Initial property was not set correctly", "10K", entry.getValue()); |
| |
| } |
| } |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void testCompactions() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // compact |
| ts.exec("createtable " + table); |
| |
| String tableId = getTableId(table); |
| |
| // make two files |
| ts.exec("insert a b c d"); |
| ts.exec("flush -w"); |
| ts.exec("insert x y z v"); |
| ts.exec("flush -w"); |
| int oldCount = countFiles(tableId); |
| // merge two files into one |
| ts.exec("compact -t " + table + " -w"); |
| assertTrue(countFiles(tableId) < oldCount); |
| ts.exec("addsplits -t " + table + " f"); |
| // make two more files: |
| ts.exec("insert m 1 2 3"); |
| ts.exec("flush -w"); |
| ts.exec("insert n 1 2 v901"); |
| ts.exec("flush -w"); |
| List<String> oldFiles = getFiles(tableId); |
| |
| // at this point there are 4 files in the default tablet |
| assertEquals("Files that were found: " + oldFiles, 4, oldFiles.size()); |
| |
| // compact some data: |
| ts.exec("compact -b g -e z -w"); |
| assertEquals(2, countFiles(tableId)); |
| ts.exec("compact -w"); |
| assertEquals(2, countFiles(tableId)); |
| ts.exec("merge --all -t " + table); |
| ts.exec("compact -w"); |
| assertEquals(1, countFiles(tableId)); |
| |
| // test compaction strategy |
| ts.exec("insert z 1 2 v900"); |
| ts.exec("compact -w -s " + TestCompactionStrategy.class.getName() |
| + " -sc inputPrefix=F,dropPrefix=A"); |
| assertEquals(1, countFiles(tableId)); |
| ts.exec("scan", true, "v900", true); |
| ts.exec("scan", true, "v901", false); |
| |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void testCompactionSelection() throws Exception { |
| final String table = name.getMethodName(); |
| final String clone = table + "_clone"; |
| |
| ts.exec("createtable " + table); |
| ts.exec("insert a b c d"); |
| ts.exec("flush -w"); |
| ts.exec("insert x y z v"); |
| ts.exec("flush -w"); |
| |
| ts.exec("clonetable -s " + Property.TABLE_MAJC_RATIO.getKey() + "=10 " + table + " " + clone); |
| |
| ts.exec("table " + clone); |
| ts.exec("insert m n l o"); |
| ts.exec("flush -w"); |
| |
| String tableId = getTableId(table); |
| String cloneId = getTableId(clone); |
| |
| assertEquals(3, countFiles(cloneId)); |
| |
| // compact only files from src table |
| ts.exec("compact -t " + clone + " -w --sf-epath .*tables/" + tableId + ".*"); |
| |
| assertEquals(2, countFiles(cloneId)); |
| |
| ts.exec("insert r s t u"); |
| ts.exec("flush -w"); |
| |
| assertEquals(3, countFiles(cloneId)); |
| |
| // compact all flush files |
| ts.exec("compact -t " + clone + " -w --sf-ename F.*"); |
| |
| assertEquals(2, countFiles(cloneId)); |
| |
| // create two large files |
| Random rand = new SecureRandom(); |
| StringBuilder sb = new StringBuilder("insert b v q "); |
| for (int i = 0; i < 10000; i++) { |
| sb.append('a' + rand.nextInt(26)); |
| } |
| |
| ts.exec(sb.toString()); |
| ts.exec("flush -w"); |
| |
| ts.exec(sb.toString()); |
| ts.exec("flush -w"); |
| |
| assertEquals(4, countFiles(cloneId)); |
| |
| // compact only small files |
| ts.exec("compact -t " + clone + " -w --sf-lt-esize 1000"); |
| |
| assertEquals(3, countFiles(cloneId)); |
| |
| // compact large files if 3 or more |
| ts.exec("compact -t " + clone + " -w --sf-gt-esize 1K --min-files 3"); |
| |
| assertEquals(3, countFiles(cloneId)); |
| |
| // compact large files if 2 or more |
| ts.exec("compact -t " + clone + " -w --sf-gt-esize 1K --min-files 2"); |
| |
| assertEquals(2, countFiles(cloneId)); |
| |
| // compact if tablet has 3 or more files |
| ts.exec("compact -t " + clone + " -w --min-files 3"); |
| |
| assertEquals(2, countFiles(cloneId)); |
| |
| // compact if tablet has 2 or more files |
| ts.exec("compact -t " + clone + " -w --min-files 2"); |
| |
| assertEquals(1, countFiles(cloneId)); |
| |
| // create two small and one large flush files in order to test AND |
| ts.exec(sb.toString()); |
| ts.exec("flush -w"); |
| |
| ts.exec("insert m n l o"); |
| ts.exec("flush -w"); |
| |
| ts.exec("insert m n l o"); |
| ts.exec("flush -w"); |
| |
| assertEquals(4, countFiles(cloneId)); |
| |
| // should only compact two small flush files leaving large flush file |
| ts.exec("compact -t " + clone + " -w --sf-ename F.* --sf-lt-esize 1K"); |
| |
| assertEquals(3, countFiles(cloneId)); |
| |
| String clone2 = table + "_clone_2"; |
| ts.exec("clonetable -s" |
| + " table.sampler.opt.hasher=murmur3_32,table.sampler.opt.modulus=7,table.sampler=" |
| + RowSampler.class.getName() + " " + clone + " " + clone2); |
| String clone2Id = getTableId(clone2); |
| |
| assertEquals(3, countFiles(clone2Id)); |
| |
| ts.exec("table " + clone2); |
| ts.exec("insert v n l o"); |
| ts.exec("flush -w"); |
| |
| ts.exec("insert x n l o"); |
| ts.exec("flush -w"); |
| |
| assertEquals(5, countFiles(clone2Id)); |
| |
| ts.exec("compact -t " + clone2 + " -w --sf-no-sample"); |
| |
| assertEquals(3, countFiles(clone2Id)); |
| } |
| |
| @Test |
| public void testCompactionSelectionAndStrategy() throws Exception { |
| |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table); |
| |
| // expect this to fail |
| ts.exec("compact -t " + table + " -w --sf-ename F.* -s " |
| + TestCompactionStrategy.class.getName() + " -sc inputPrefix=F,dropPrefix=A", false); |
| } |
| |
| @Test |
| public void testScanScample() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // compact |
| ts.exec("createtable " + table); |
| |
| ts.exec("insert 9255 doc content 'abcde'"); |
| ts.exec("insert 9255 doc url file://foo.txt"); |
| ts.exec("insert 8934 doc content 'accumulo scales'"); |
| ts.exec("insert 8934 doc url file://accumulo_notes.txt"); |
| ts.exec("insert 2317 doc content 'milk, eggs, bread, parmigiano-reggiano'"); |
| ts.exec("insert 2317 doc url file://groceries/9.txt"); |
| ts.exec("insert 3900 doc content 'EC2 ate my homework'"); |
| ts.exec("insert 3900 doc uril file://final_project.txt"); |
| |
| String clone1 = table + "_clone_1"; |
| ts.exec("clonetable -s" |
| + " table.sampler.opt.hasher=murmur3_32,table.sampler.opt.modulus=3,table.sampler=" |
| + RowSampler.class.getName() + " " + table + " " + clone1); |
| |
| ts.exec("compact -t " + clone1 + " -w --sf-no-sample"); |
| |
| ts.exec("table " + clone1); |
| ts.exec("scan --sample", true, "parmigiano-reggiano", true); |
| ts.exec("grep --sample reg", true, "parmigiano-reggiano", true); |
| ts.exec("scan --sample", true, "accumulo", false); |
| ts.exec("grep --sample acc", true, "accumulo", false); |
| |
| // create table where table sample config differs from whats in file |
| String clone2 = table + "_clone_2"; |
| ts.exec("clonetable -s" |
| + " table.sampler.opt.hasher=murmur3_32,table.sampler.opt.modulus=2,table.sampler=" |
| + RowSampler.class.getName() + " " + clone1 + " " + clone2); |
| |
| ts.exec("table " + clone2); |
| ts.exec("scan --sample", false, "SampleNotPresentException", true); |
| ts.exec("grep --sample reg", false, "SampleNotPresentException", true); |
| |
| ts.exec("compact -t " + clone2 + " -w --sf-no-sample"); |
| |
| for (String expected : Arrays.asList("2317", "3900", "9255")) { |
| ts.exec("scan --sample", true, expected, true); |
| ts.exec("grep --sample " + expected.substring(0, 2), true, expected, true); |
| } |
| |
| ts.exec("scan --sample", true, "8934", false); |
| ts.exec("grep --sample 89", true, "8934", false); |
| } |
| |
| @Test |
| public void constraint() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // constraint |
| ts.exec("constraint -l -t " + MetadataTable.NAME + "", true, "MetadataConstraints=1", true); |
| ts.exec("createtable " + table + " -evc"); |
| |
| // Make sure the table is fully propagated through zoocache |
| getTableId(table); |
| |
| ts.exec("constraint -l -t " + table, true, "VisibilityConstraint=2", true); |
| ts.exec("constraint -t " + table + " -d 2", true, "Removed constraint 2 from table " + table); |
| // wait for zookeeper updates to propagate |
| sleepUninterruptibly(1, TimeUnit.SECONDS); |
| ts.exec("constraint -l -t " + table, true, "VisibilityConstraint=2", false); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void deletemany() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // deletemany |
| ts.exec("createtable " + table); |
| make10(); |
| assertEquals(10, countkeys(table)); |
| ts.exec("deletemany -f -b row8"); |
| assertEquals(8, countkeys(table)); |
| ts.exec("scan -t " + table + " -np", true, "row8", false); |
| make10(); |
| ts.exec("deletemany -f -b row4 -e row5"); |
| assertEquals(8, countkeys(table)); |
| make10(); |
| ts.exec("deletemany -f -c cf:col4,cf:col5"); |
| assertEquals(8, countkeys(table)); |
| make10(); |
| ts.exec("deletemany -f -r row3"); |
| assertEquals(9, countkeys(table)); |
| make10(); |
| ts.exec("deletemany -f -r row3"); |
| assertEquals(9, countkeys(table)); |
| make10(); |
| ts.exec("deletemany -f -b row3 -be -e row5 -ee"); |
| assertEquals(9, countkeys(table)); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void deleterows() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table); |
| final String tableId = getTableId(table); |
| |
| // deleterows |
| int base = countFiles(tableId); |
| assertEquals(0, base); |
| |
| log.info("Adding 2 splits"); |
| ts.exec("addsplits row5 row7"); |
| |
| log.info("Writing 10 records"); |
| make10(); |
| |
| log.info("Flushing table"); |
| ts.exec("flush -w -t " + table); |
| log.info("Table flush completed"); |
| |
| // One of the tablets we're writing to might migrate inbetween writing data which would create a |
| // 2nd file for that tablet |
| // If we notice this, compact and then move on. |
| List<String> files = getFiles(tableId); |
| if (files.size() > 3) { |
| log.info("More than 3 files were found, compacting before proceeding"); |
| ts.exec("compact -w -t " + table); |
| files = getFiles(tableId); |
| assertEquals("Expected to only find 3 files after compaction: " + files, 3, files.size()); |
| } |
| |
| assertNotNull(files); |
| assertEquals("Found the following files: " + files, 3, files.size()); |
| ts.exec("deleterows -t " + table + " -b row5 -e row7"); |
| assertEquals(2, countFiles(tableId)); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void groups() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table); |
| ts.exec("setgroups -t " + table + " alpha=a,b,c num=3,2,1"); |
| ts.exec("getgroups -t " + table, true, "alpha=a,b,c", true); |
| ts.exec("getgroups -t " + table, true, "num=1,2,3", true); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void formatter() throws Exception { |
| ts.exec("createtable formatter_test", true); |
| ts.exec("table formatter_test", true); |
| ts.exec("insert row cf cq 1234abcd", true); |
| ts.exec("insert row cf1 cq1 9876fedc", true); |
| ts.exec("insert row2 cf cq 13579bdf", true); |
| ts.exec("insert row2 cf1 cq 2468ace", true); |
| |
| ArrayList<String> expectedDefault = new ArrayList<>(4); |
| expectedDefault.add("row cf:cq [] 1234abcd"); |
| expectedDefault.add("row cf1:cq1 [] 9876fedc"); |
| expectedDefault.add("row2 cf:cq [] 13579bdf"); |
| expectedDefault.add("row2 cf1:cq [] 2468ace"); |
| ArrayList<String> actualDefault = new ArrayList<>(4); |
| boolean isFirst = true; |
| for (String s : ts.exec("scan -np", true).split("[\n\r]+")) { |
| if (isFirst) { |
| isFirst = false; |
| } else { |
| actualDefault.add(s); |
| } |
| } |
| |
| ArrayList<String> expectedFormatted = new ArrayList<>(4); |
| expectedFormatted.add("row cf:cq [] 0x31 0x32 0x33 0x34 0x61 0x62 0x63 0x64"); |
| expectedFormatted.add("row cf1:cq1 [] 0x39 0x38 0x37 0x36 0x66 0x65 0x64 0x63"); |
| expectedFormatted.add("row2 cf:cq [] 0x31 0x33 0x35 0x37 0x39 0x62 0x64 0x66"); |
| expectedFormatted.add("row2 cf1:cq [] 0x32 0x34 0x36 0x38 0x61 0x63 0x65"); |
| ts.exec("formatter -t formatter_test -f " + HexFormatter.class.getName(), true); |
| ArrayList<String> actualFormatted = new ArrayList<>(4); |
| isFirst = true; |
| for (String s : ts.exec("scan -np", true).split("[\n\r]+")) { |
| if (isFirst) { |
| isFirst = false; |
| } else { |
| actualFormatted.add(s); |
| } |
| } |
| |
| ts.exec("deletetable -f formatter_test", true); |
| |
| assertTrue(Iterables.elementsEqual(expectedDefault, new ArrayList<>(actualDefault))); |
| assertTrue(Iterables.elementsEqual(expectedFormatted, new ArrayList<>(actualFormatted))); |
| } |
| |
| /** |
| * Simple <code>Formatter</code> that will convert each character in the Value from decimal to |
| * hexadecimal. Will automatically skip over characters in the value which do not fall within the |
| * [0-9,a-f] range. |
| * |
| * <p> |
| * Example: <code>'0'</code> will be displayed as <code>'0x30'</code> |
| */ |
| public static class HexFormatter implements Formatter { |
| private Iterator<Entry<Key,Value>> iter = null; |
| private FormatterConfig config; |
| |
| private static final String tab = "\t"; |
| private static final String newline = "\n"; |
| |
| @Override |
| public boolean hasNext() { |
| return this.iter.hasNext(); |
| } |
| |
| @Override |
| public String next() { |
| final Entry<Key,Value> entry = iter.next(); |
| |
| String key; |
| |
| // Observe the timestamps |
| if (config.willPrintTimestamps()) { |
| key = entry.getKey().toString(); |
| } else { |
| key = entry.getKey().toStringNoTime(); |
| } |
| |
| final Value v = entry.getValue(); |
| |
| // Approximate how much space we'll need |
| final StringBuilder sb = new StringBuilder(key.length() + v.getSize() * 5); |
| |
| sb.append(key).append(tab); |
| |
| for (byte b : v.get()) { |
| if ((b >= 48 && b <= 57) || (b >= 97 && b <= 102)) { |
| sb.append(String.format("0x%x ", (int) b)); |
| } |
| } |
| |
| return sb.toString().trim() + newline; |
| } |
| |
| @Override |
| public void remove() {} |
| |
| @Override |
| public void initialize(final Iterable<Entry<Key,Value>> scanner, final FormatterConfig config) { |
| this.iter = scanner.iterator(); |
| this.config = new FormatterConfig(config); |
| } |
| } |
| |
| @Test |
| public void extensions() throws Exception { |
| String extName = "ExampleShellExtension"; |
| |
| // check for example extension |
| ts.exec("help", true, extName, false); |
| ts.exec("extensions -l", true, extName, false); |
| |
| // enable extensions and check for example |
| ts.exec("extensions -e", true); |
| ts.exec("extensions -l", true, extName, true); |
| ts.exec("help", true, extName, true); |
| |
| // test example extension command |
| ts.exec(extName + "::debug", true, "This is a test", true); |
| |
| // disable extensions and check for example |
| ts.exec("extensions -d", true); |
| ts.exec("extensions -l", true, extName, false); |
| ts.exec("help", true, extName, false); |
| |
| // ensure extensions are really disabled |
| ts.exec(extName + "::debug", true, "Unknown command", true); |
| } |
| |
| @Test |
| public void grep() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table, true); |
| make10(); |
| ts.exec("grep row[123]", true, "row1", false); |
| ts.exec("grep row5", true, "row5", true); |
| ts.exec("deletetable -f " + table, true); |
| } |
| |
| @Test |
| public void help() throws Exception { |
| ts.exec("help -np", true, "Help Commands", true); |
| ts.exec("?", true, "Help Commands", true); |
| for (String c : ("bye exit quit " + "about help info ? " |
| + "deleteiter deletescaniter listiter setiter setscaniter " |
| + "grant revoke systempermissions tablepermissions userpermissions " + "execfile history " |
| + "authenticate cls clear notable sleep table user whoami " |
| + "clonetable config createtable deletetable droptable du exporttable " |
| + "importtable offline online renametable tables " |
| + "addsplits compact constraint flush getgropus getsplits merge setgroups " |
| + "addauths createuser deleteuser dropuser getauths passwd setauths users " |
| + "delete deletemany deleterows egrep formatter interpreter grep " |
| + "importdirectory insert maxrow scan").split(" ")) { |
| ts.exec("help " + c, true); |
| } |
| } |
| |
| // @Test(timeout = 45000) |
| public void history() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("history -c", true); |
| ts.exec("createtable " + table); |
| ts.exec("deletetable -f " + table); |
| ts.exec("history", true, table, true); |
| ts.exec("history", true, "history", true); |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "path provided by test") |
| @Test |
| public void importDirectory() throws Exception { |
| final String table = name.getMethodName(); |
| |
| Configuration conf = new Configuration(); |
| FileSystem fs = FileSystem.get(conf); |
| File importDir = new File(rootPath, "import"); |
| assertTrue(importDir.mkdir()); |
| String even = new File(importDir, "even.rf").toString(); |
| String odd = new File(importDir, "odd.rf").toString(); |
| File errorsDir = new File(rootPath, "errors"); |
| assertTrue(errorsDir.mkdir()); |
| fs.mkdirs(new Path(errorsDir.toString())); |
| AccumuloConfiguration aconf = DefaultConfiguration.getInstance(); |
| FileSKVWriter evenWriter = FileOperations.getInstance().newWriterBuilder() |
| .forFile(even, fs, conf, CryptoServiceFactory.newDefaultInstance()) |
| .withTableConfiguration(aconf).build(); |
| evenWriter.startDefaultLocalityGroup(); |
| FileSKVWriter oddWriter = FileOperations.getInstance().newWriterBuilder() |
| .forFile(odd, fs, conf, CryptoServiceFactory.newDefaultInstance()) |
| .withTableConfiguration(aconf).build(); |
| oddWriter.startDefaultLocalityGroup(); |
| long timestamp = System.currentTimeMillis(); |
| Text cf = new Text("cf"); |
| Text cq = new Text("cq"); |
| Value value = new Value("value".getBytes()); |
| for (int i = 0; i < 100; i += 2) { |
| Key key = new Key(new Text(String.format("%8d", i)), cf, cq, timestamp); |
| evenWriter.append(key, value); |
| key = new Key(new Text(String.format("%8d", i + 1)), cf, cq, timestamp); |
| oddWriter.append(key, value); |
| } |
| evenWriter.close(); |
| oddWriter.close(); |
| assertEquals(0, ts.shell.getExitCode()); |
| ts.exec("createtable " + table, true); |
| ts.exec("importdirectory " + importDir + " " + errorsDir + " true", true); |
| ts.exec("scan -r 00000000", true, "00000000", true); |
| ts.exec("scan -r 00000099", true, "00000099", true); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| @Test |
| public void info() throws Exception { |
| ts.exec("info", true, Constants.VERSION, true); |
| } |
| |
| @Test |
| public void interpreter() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table, true); |
| ts.exec("interpreter -l", true, "HexScan", false); |
| ts.exec("insert \\x02 cf cq value", true); |
| ts.exec("scan -b 02", true, "value", false); |
| ts.exec("interpreter -i org.apache.accumulo.core.util.interpret.HexScanInterpreter", true); |
| // Need to allow time for this to propagate through zoocache/zookeeper |
| sleepUninterruptibly(3, TimeUnit.SECONDS); |
| |
| ts.exec("interpreter -l", true, "HexScan", true); |
| ts.exec("scan -b 02", true, "value", true); |
| ts.exec("deletetable -f " + table, true); |
| } |
| |
| @Test |
| public void listcompactions() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table, true); |
| ts.exec( |
| "config -t " + table |
| + " -s table.iterator.minc.slow=30,org.apache.accumulo.test.functional.SlowIterator", |
| true); |
| ts.exec("config -t " + table + " -s table.iterator.minc.slow.opt.sleepTime=1000", true); |
| ts.exec("insert a cf cq value", true); |
| ts.exec("insert b cf cq value", true); |
| ts.exec("insert c cf cq value", true); |
| ts.exec("insert d cf cq value", true); |
| ts.exec("flush -t " + table, true); |
| ts.exec("sleep 0.2", true); |
| ts.exec("listcompactions", true, "default_tablet"); |
| String[] lines = ts.output.get().split("\n"); |
| String last = lines[lines.length - 1]; |
| String[] parts = last.split("\\|"); |
| assertEquals(12, parts.length); |
| ts.exec("deletetable -f " + table, true); |
| } |
| |
| @Test |
| public void maxrow() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table, true); |
| ts.exec("insert a cf cq value", true); |
| ts.exec("insert b cf cq value", true); |
| ts.exec("insert ccc cf cq value", true); |
| ts.exec("insert zzz cf cq value", true); |
| ts.exec("maxrow", true, "zzz", true); |
| ts.exec("delete zzz cf cq", true); |
| ts.exec("maxrow", true, "ccc", true); |
| ts.exec("deletetable -f " + table, true); |
| } |
| |
| @Test |
| public void merge() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table); |
| ts.exec("addsplits a m z"); |
| ts.exec("getsplits", true, "z", true); |
| ts.exec("merge --all", true); |
| ts.exec("getsplits", true, "z", false); |
| ts.exec("deletetable -f " + table); |
| ts.exec("getsplits -t " + MetadataTable.NAME + "", true); |
| assertEquals(2, ts.output.get().split("\n").length); |
| ts.exec("getsplits -t accumulo.root", true); |
| assertEquals(1, ts.output.get().split("\n").length); |
| ts.exec("merge --all -t " + MetadataTable.NAME + ""); |
| ts.exec("getsplits -t " + MetadataTable.NAME + "", true); |
| assertEquals(1, ts.output.get().split("\n").length); |
| } |
| |
| @Test |
| public void ping() throws Exception { |
| for (int i = 0; i < 10; i++) { |
| ts.exec("ping", true, "OK", true); |
| // wait for both tservers to start up |
| if (ts.output.get().split("\n").length == 3) |
| break; |
| sleepUninterruptibly(1, TimeUnit.SECONDS); |
| |
| } |
| assertEquals(2, ts.output.get().split("\n").length); |
| } |
| |
| @Test |
| public void renametable() throws Exception { |
| final String table = name.getMethodName() + "1", rename = name.getMethodName() + "2"; |
| |
| ts.exec("createtable " + table); |
| ts.exec("insert this is a value"); |
| ts.exec("renametable " + table + " " + rename); |
| ts.exec("tables", true, rename, true); |
| ts.exec("tables", true, table, false); |
| ts.exec("scan -t " + rename, true, "value", true); |
| ts.exec("deletetable -f " + rename, true); |
| } |
| |
| @Test |
| public void tables() throws Exception { |
| final String table = name.getMethodName(), table1 = table + "_z", table2 = table + "_a"; |
| ts.exec("createtable " + table1); |
| ts.exec("createtable " + table2); |
| ts.exec("notable"); |
| String lst = ts.exec("tables -l"); |
| assertTrue(lst.indexOf(table2) < lst.indexOf(table1)); |
| lst = ts.exec("tables -l -s"); |
| assertTrue(lst.indexOf(table1) < lst.indexOf(table2)); |
| } |
| |
| @Test |
| public void systempermission() throws Exception { |
| ts.exec("systempermissions"); |
| assertEquals(12, ts.output.get().split("\n").length - 1); |
| ts.exec("tablepermissions", true); |
| assertEquals(7, ts.output.get().split("\n").length - 1); |
| } |
| |
| @Test |
| public void listscans() throws Exception { |
| final String table = name.getMethodName(); |
| |
| ts.exec("createtable " + table, true); |
| |
| // Should be about a 3 second scan |
| for (int i = 0; i < 6; i++) { |
| ts.exec("insert " + i + " cf cq value", true); |
| } |
| |
| try (AccumuloClient accumuloClient = Accumulo.newClient().from(getClientProps()).build(); |
| Scanner s = accumuloClient.createScanner(table, Authorizations.EMPTY)) { |
| IteratorSetting cfg = new IteratorSetting(30, SlowIterator.class); |
| SlowIterator.setSleepTime(cfg, 500); |
| s.addScanIterator(cfg); |
| |
| Thread thread = new Thread(() -> Iterators.size(s.iterator())); |
| thread.start(); |
| |
| List<String> scans = new ArrayList<>(); |
| // Try to find the active scan for about 15seconds |
| for (int i = 0; i < 50 && scans.isEmpty(); i++) { |
| String currentScans = ts.exec("listscans", true); |
| log.info("Got output from listscans:\n{}", currentScans); |
| String[] lines = currentScans.split("\n"); |
| for (int scanOffset = 2; scanOffset < lines.length; scanOffset++) { |
| String currentScan = lines[scanOffset]; |
| if (currentScan.contains(table)) { |
| log.info("Retaining scan: {}", currentScan); |
| scans.add(currentScan); |
| } else { |
| log.info("Ignoring scan because of wrong table: {}", currentScan); |
| } |
| } |
| sleepUninterruptibly(300, TimeUnit.MILLISECONDS); |
| } |
| thread.join(); |
| |
| assertFalse("Could not find any active scans over table " + table, scans.isEmpty()); |
| |
| for (String scan : scans) { |
| if (!scan.contains("RUNNING")) { |
| log.info("Ignoring scan because it doesn't contain 'RUNNING': {}", scan); |
| continue; |
| } |
| String[] parts = scan.split("\\|"); |
| assertEquals("Expected 14 colums, but found " + parts.length + " instead for '" |
| + Arrays.toString(parts) + "'", 14, parts.length); |
| String tserver = parts[0].trim(); |
| // TODO: any way to tell if the client address is accurate? could be local IP, host, |
| // loopback...? |
| String hostPortPattern = ".+:\\d+"; |
| assertTrue(tserver.matches(hostPortPattern)); |
| assertTrue(accumuloClient.instanceOperations().getTabletServers().contains(tserver)); |
| String client = parts[1].trim(); |
| assertTrue(client + " does not match " + hostPortPattern, client.matches(hostPortPattern)); |
| // Scan ID should be a long (throwing an exception if it fails to parse) |
| Long.parseLong(parts[11].trim()); |
| } |
| } |
| ts.exec("deletetable -f " + table, true); |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "path provided by test") |
| @Test |
| public void testPertableClasspath() throws Exception { |
| final String table = name.getMethodName(); |
| |
| File fooFilterJar = File.createTempFile("FooFilter", ".jar", new File(rootPath)); |
| |
| FileUtils.copyInputStreamToFile(this.getClass().getResourceAsStream("/FooFilter.jar"), |
| fooFilterJar); |
| fooFilterJar.deleteOnExit(); |
| |
| File fooConstraintJar = File.createTempFile("FooConstraint", ".jar", new File(rootPath)); |
| FileUtils.copyInputStreamToFile(this.getClass().getResourceAsStream("/FooConstraint.jar"), |
| fooConstraintJar); |
| fooConstraintJar.deleteOnExit(); |
| |
| ts.exec("config -s " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY.getKey() + "cx1=" |
| + fooFilterJar.toURI() + "," + fooConstraintJar.toURI(), true); |
| |
| ts.exec("createtable " + table, true); |
| ts.exec("config -t " + table + " -s " + Property.TABLE_CLASSPATH.getKey() + "=cx1", true); |
| |
| sleepUninterruptibly(200, TimeUnit.MILLISECONDS); |
| |
| // We can't use the setiter command as Filter implements OptionDescriber which |
| // forces us to enter more input that I don't know how to input |
| // Instead, we can just manually set the property on the table. |
| ts.exec("config -t " + table + " -s " + Property.TABLE_ITERATOR_PREFIX.getKey() |
| + "scan.foo=10,org.apache.accumulo.test.FooFilter"); |
| |
| ts.exec("insert foo f q v", true); |
| |
| sleepUninterruptibly(100, TimeUnit.MILLISECONDS); |
| |
| ts.exec("scan -np", true, "foo", false); |
| |
| ts.exec("constraint -a FooConstraint", true); |
| |
| ts.exec("offline -w " + table); |
| ts.exec("online -w " + table); |
| |
| ts.exec("table " + table, true); |
| ts.exec("insert foo f q v", false); |
| ts.exec("insert ok foo q v", true); |
| |
| ts.exec("deletetable -f " + table, true); |
| ts.exec("config -d " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY.getKey() + "cx1"); |
| |
| } |
| |
| @Test |
| public void trace() throws Exception { |
| // Make sure to not collide with the "trace" table |
| final String table = name.getMethodName() + "Test"; |
| |
| ts.exec("trace on", true); |
| ts.exec("createtable " + table, true); |
| ts.exec("insert a b c value", true); |
| ts.exec("scan -np", true, "value", true); |
| ts.exec("deletetable -f " + table); |
| ts.exec("sleep 1"); |
| String trace = ts.exec("trace off"); |
| System.out.println(trace); |
| assertTrue(trace.contains("sendMutations")); |
| assertTrue(trace.contains("startScan")); |
| assertTrue(trace.contains("DeleteTable")); |
| } |
| |
| @Test |
| public void badLogin() throws Exception { |
| // Can't run with Kerberos, can't switch identity in shell presently |
| Assume.assumeTrue(getToken() instanceof PasswordToken); |
| ts.input.set(getRootPassword() + "\n"); |
| String err = ts.exec("user NoSuchUser", false); |
| assertTrue(err.contains("BAD_CREDENTIALS for user NoSuchUser")); |
| } |
| |
| @Test |
| public void namespaces() throws Exception { |
| ts.exec("namespaces", true, "\"\"", true); // default namespace, displayed as quoted empty |
| // string |
| ts.exec("namespaces", true, Namespace.ACCUMULO.name(), true); |
| ts.exec("createnamespace thing1", true); |
| String namespaces = ts.exec("namespaces"); |
| assertTrue(namespaces.contains("thing1")); |
| |
| ts.exec("renamenamespace thing1 thing2"); |
| namespaces = ts.exec("namespaces"); |
| assertTrue(namespaces.contains("thing2")); |
| assertTrue(!namespaces.contains("thing1")); |
| |
| // can't delete a namespace that still contains tables, unless you do -f |
| ts.exec("createtable thing2.thingy", true); |
| ts.exec("deletenamespace thing2"); |
| ts.exec("y"); |
| ts.exec("namespaces", true, "thing2", true); |
| |
| ts.exec("du -ns thing2", true, "thing2.thingy", true); |
| |
| // all "TableOperation" commands can take a namespace |
| ts.exec("offline -ns thing2", true); |
| ts.exec("online -ns thing2", true); |
| ts.exec("flush -ns thing2", true); |
| ts.exec("compact -ns thing2", true); |
| ts.exec("createnamespace testers3", true); |
| ts.exec("createtable testers3.1", true); |
| ts.exec("createtable testers3.2", true); |
| ts.exec("deletetable -ns testers3 -f", true); |
| ts.exec("tables", true, "testers3.1", false); |
| ts.exec("namespaces", true, "testers3", true); |
| ts.exec("deletenamespace testers3 -f", true); |
| ts.input.set("true\n\n\n\nSTRING\n"); |
| ts.exec("setiter -ns thing2 -scan -class " + SUMMING_COMBINER_ITERATOR + " -p 10 -n name", |
| true); |
| ts.exec("listiter -ns thing2 -scan", true, "Summing", true); |
| ts.exec("deleteiter -ns thing2 -n name -scan", true); |
| ts.exec("createuser dude"); |
| ts.exec("pass"); |
| ts.exec("pass"); |
| ts.exec("grant Namespace.CREATE_TABLE -ns thing2 -u dude", true); |
| ts.exec("revoke Namespace.CREATE_TABLE -ns thing2 -u dude", true); |
| |
| // properties override and such |
| ts.exec("config -ns thing2 -s table.file.max=44444", true); |
| ts.exec("config -ns thing2", true, "44444", true); |
| ts.exec("config -t thing2.thingy", true, "44444", true); |
| ts.exec("config -t thing2.thingy -s table.file.max=55555", true); |
| ts.exec("config -t thing2.thingy", true, "55555", true); |
| |
| // can copy properties when creating |
| ts.exec("createnamespace thing3 -cc thing2", true); |
| ts.exec("config -ns thing3", true, "44444", true); |
| |
| ts.exec("deletenamespace -f thing2", true); |
| ts.exec("namespaces", true, "thing2", false); |
| ts.exec("tables", true, "thing2.thingy", false); |
| |
| // put constraints on a namespace |
| ts.exec("constraint -ns thing3 -a org.apache.accumulo.test.constraints.NumericValueConstraint", |
| true); |
| ts.exec("createtable thing3.constrained", true); |
| ts.exec("table thing3.constrained", true); |
| ts.exec("constraint -d 1"); |
| // should fail |
| ts.exec("constraint -l", true, "NumericValueConstraint", true); |
| ts.exec("insert r cf cq abc", false); |
| ts.exec("constraint -ns thing3 -d 1"); |
| ts.exec("sleep 1"); |
| ts.exec("insert r cf cq abc", true); |
| } |
| |
| private int countkeys(String table) throws IOException { |
| ts.exec("scan -np -t " + table); |
| return ts.output.get().split("\n").length - 1; |
| } |
| |
| @Test |
| public void scans() throws Exception { |
| ts.exec("createtable t"); |
| make10(); |
| String result = ts.exec("scan -np -b row1 -e row1"); |
| assertEquals(2, result.split("\n").length); |
| result = ts.exec("scan -np -b row3 -e row5"); |
| assertEquals(4, result.split("\n").length); |
| result = ts.exec("scan -np -r row3"); |
| assertEquals(2, result.split("\n").length); |
| result = ts.exec("scan -np -b row:"); |
| assertEquals(1, result.split("\n").length); |
| result = ts.exec("scan -np -b row"); |
| assertEquals(11, result.split("\n").length); |
| result = ts.exec("scan -np -e row:"); |
| assertEquals(11, result.split("\n").length); |
| ts.exec("deletetable -f t"); |
| } |
| |
| @Test |
| public void scansWithClassLoaderContext() throws IOException { |
| try { |
| Class.forName(VALUE_REVERSING_ITERATOR); |
| fail("ValueReversingIterator already on the classpath"); |
| } catch (ClassNotFoundException e) { |
| // expected; iterator is already on the class path |
| } |
| ts.exec("createtable t"); |
| // Assert that the TabletServer does not know anything about our class |
| String result = |
| ts.exec("setiter -scan -n reverse -t t -p 21 -class " + VALUE_REVERSING_ITERATOR); |
| assertTrue(result.contains("class not found")); |
| make10(); |
| setupFakeContextPath(); |
| // Add the context to the table so that setiter works. |
| result = ts.exec("config -s " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY + FAKE_CONTEXT + "=" |
| + FAKE_CONTEXT_CLASSPATH); |
| assertEquals("root@miniInstance t> config -s " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY |
| + FAKE_CONTEXT + "=" + FAKE_CONTEXT_CLASSPATH + "\n", result); |
| |
| result = ts.exec("config -t t -s table.classpath.context=" + FAKE_CONTEXT); |
| assertEquals( |
| "root@miniInstance t> config -t t -s table.classpath.context=" + FAKE_CONTEXT + "\n", |
| result); |
| |
| result = ts.exec("setshelliter -pn baz -n reverse -p 21 -class " + VALUE_REVERSING_ITERATOR); |
| assertTrue(result.contains("The iterator class does not implement OptionDescriber")); |
| |
| // The implementation of ValueReversingIterator in the FAKE context does nothing, the value is |
| // not reversed. |
| result = ts.exec("scan -pn baz -np -b row1 -e row1"); |
| assertEquals(2, result.split("\n").length); |
| assertTrue(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -b row3 -e row5"); |
| assertEquals(4, result.split("\n").length); |
| assertTrue(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -r row3"); |
| assertEquals(2, result.split("\n").length); |
| assertTrue(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -b row:"); |
| assertEquals(1, result.split("\n").length); |
| result = ts.exec("scan -pn baz -np -b row"); |
| assertEquals(11, result.split("\n").length); |
| assertTrue(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -e row:"); |
| assertEquals(11, result.split("\n").length); |
| assertTrue(result.contains("value")); |
| |
| setupRealContextPath(); |
| // Define a new classloader context, but don't set it on the table |
| result = ts.exec("config -s " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY + REAL_CONTEXT + "=" |
| + REAL_CONTEXT_CLASSPATH); |
| assertEquals("root@miniInstance t> config -s " + Property.VFS_CONTEXT_CLASSPATH_PROPERTY |
| + REAL_CONTEXT + "=" + REAL_CONTEXT_CLASSPATH + "\n", result); |
| // Override the table classloader context with the REAL implementation of |
| // ValueReversingIterator, which does reverse the value. |
| result = ts.exec("scan -pn baz -np -b row1 -e row1 -cc " + REAL_CONTEXT); |
| assertEquals(2, result.split("\n").length); |
| assertTrue(result.contains("eulav")); |
| assertFalse(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -b row3 -e row5 -cc " + REAL_CONTEXT); |
| assertEquals(4, result.split("\n").length); |
| assertTrue(result.contains("eulav")); |
| assertFalse(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -r row3 -cc " + REAL_CONTEXT); |
| assertEquals(2, result.split("\n").length); |
| assertTrue(result.contains("eulav")); |
| assertFalse(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -b row: -cc " + REAL_CONTEXT); |
| assertEquals(1, result.split("\n").length); |
| result = ts.exec("scan -pn baz -np -b row -cc " + REAL_CONTEXT); |
| assertEquals(11, result.split("\n").length); |
| assertTrue(result.contains("eulav")); |
| assertFalse(result.contains("value")); |
| result = ts.exec("scan -pn baz -np -e row: -cc " + REAL_CONTEXT); |
| assertEquals(11, result.split("\n").length); |
| assertTrue(result.contains("eulav")); |
| assertFalse(result.contains("value")); |
| ts.exec("deletetable -f t"); |
| } |
| |
| /** |
| * The purpose of this test is to verify that you can successfully scan a table with a regular |
| * iterator. It was written to verify that the changes made while updating the setshelliter |
| * command did not break the existing setiter capabilities. It tests that a table can be scanned |
| * with an iterator both while within a table context and also while in the 'notable' context. |
| */ |
| @Test |
| public void testScanTableWithIterSetWithoutProfile() throws Exception { |
| final String table = name.getMethodName(); |
| |
| // create a table |
| ts.exec("createtable " + table, true); |
| |
| // add some data |
| ts.exec("insert foo a b c", true); |
| ts.exec("scan", true, "foo a:b [] c"); |
| |
| // create a normal iterator while in current table context |
| ts.input.set("\n1000\n\n"); |
| ts.exec("setiter -scan -n itname -p 10 -ageoff", true); |
| |
| ts.exec("sleep 2", true); |
| // scan the created table. |
| ts.exec("scan", true, "", true); |
| ts.exec("deletetable -f " + table); |
| |
| // Repeat process but do it within the 'notable' context (after table creation and insertion) |
| // create a table |
| ts.exec("createtable " + table, true); |
| |
| // add some data |
| ts.exec("insert foo a b c", true); |
| ts.exec("notable"); |
| ts.exec("scan -t " + table, true, "foo a:b [] c"); |
| |
| // create a normal iterator which in current table context |
| ts.input.set("\n1000\n\n"); |
| ts.exec("setiter -scan -n itname -p 10 -ageoff -t " + table, true); |
| ts.exec("sleep 2", true); |
| // re-scan the table. Should not see data. |
| ts.exec("scan -t " + table, true, "", true); |
| ts.exec("deletetable -f " + table); |
| } |
| |
| private static final String FAKE_CONTEXT = "FAKE"; |
| private static final String FAKE_CONTEXT_CLASSPATH = "file://" + System.getProperty("user.dir") |
| + "/target/" + ShellServerIT.class.getSimpleName() + "-fake-iterators.jar"; |
| private static final String REAL_CONTEXT = "REAL"; |
| private static final String REAL_CONTEXT_CLASSPATH = "file://" + System.getProperty("user.dir") |
| + "/target/" + ShellServerIT.class.getSimpleName() + "-real-iterators.jar"; |
| private static final String VALUE_REVERSING_ITERATOR = |
| "org.apache.accumulo.test." + "functional.ValueReversingIterator"; |
| private static final String SUMMING_COMBINER_ITERATOR = |
| "org.apache.accumulo.core." + "iterators.user.SummingCombiner"; |
| private static final String COLUMN_FAMILY_COUNTER_ITERATOR = |
| "org.apache.accumulo.core.iterators" + ".ColumnFamilyCounter"; |
| |
| private void setupRealContextPath() throws IOException { |
| // Copy the test iterators jar to tmp |
| Path baseDir = new Path(System.getProperty("user.dir")); |
| Path targetDir = new Path(baseDir, "target"); |
| Path jarPath = new Path(targetDir, "TestJar-Iterators.jar"); |
| Path dstPath = new Path(REAL_CONTEXT_CLASSPATH); |
| FileSystem fs = SharedMiniClusterBase.getCluster().getFileSystem(); |
| fs.copyFromLocalFile(jarPath, dstPath); |
| } |
| |
| private void setupFakeContextPath() throws IOException { |
| // Copy the test iterators jar to tmp |
| Path baseDir = new Path(System.getProperty("user.dir")); |
| Path targetDir = new Path(baseDir, "target"); |
| Path classesDir = new Path(targetDir, "classes"); |
| Path jarPath = new Path(classesDir, "ShellServerIT-iterators.jar"); |
| Path dstPath = new Path(FAKE_CONTEXT_CLASSPATH); |
| FileSystem fs = SharedMiniClusterBase.getCluster().getFileSystem(); |
| fs.copyFromLocalFile(jarPath, dstPath); |
| } |
| |
| @Test |
| public void whoami() throws Exception { |
| AuthenticationToken token = getToken(); |
| assertTrue(ts.exec("whoami", true).contains(getPrincipal())); |
| // Unnecessary with Kerberos enabled, won't prompt for a password |
| if (token instanceof PasswordToken) { |
| ts.input.set("secret\nsecret\n"); |
| } |
| ts.exec("createuser test_user"); |
| ts.exec("setauths -u test_user -s 12,3,4"); |
| String auths = ts.exec("getauths -u test_user"); |
| assertTrue(auths.contains("3") && auths.contains("12") && auths.contains("4")); |
| // No support to switch users within the shell with Kerberos |
| if (token instanceof PasswordToken) { |
| ts.input.set("secret\n"); |
| ts.exec("user test_user", true); |
| assertTrue(ts.exec("whoami", true).contains("test_user")); |
| ts.input.set(getRootPassword() + "\n"); |
| ts.exec("user root", true); |
| } |
| } |
| |
| private void make10() throws IOException { |
| for (int i = 0; i < 10; i++) { |
| ts.exec(String.format("insert row%d cf col%d value", i, i)); |
| } |
| } |
| |
| private List<String> getFiles(String tableId) throws IOException { |
| ts.output.clear(); |
| |
| ts.exec( |
| "scan -t " + MetadataTable.NAME + " -np -c file -b " + tableId + " -e " + tableId + "~"); |
| |
| log.debug("countFiles(): {}", ts.output.get()); |
| |
| String[] lines = StringUtils.split(ts.output.get(), "\n"); |
| ts.output.clear(); |
| |
| if (lines.length == 0) { |
| return Collections.emptyList(); |
| } |
| |
| return Arrays.asList(Arrays.copyOfRange(lines, 1, lines.length)); |
| } |
| |
| private int countFiles(String tableId) throws IOException { |
| return getFiles(tableId).size(); |
| } |
| |
| private String getTableId(String tableName) throws Exception { |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| |
| for (int i = 0; i < 5; i++) { |
| Map<String,String> nameToId = client.tableOperations().tableIdMap(); |
| if (nameToId.containsKey(tableName)) { |
| return nameToId.get(tableName); |
| } else { |
| Thread.sleep(1000); |
| } |
| } |
| |
| fail("Could not find ID for table: " + tableName); |
| // Will never get here |
| return null; |
| } |
| } |
| |
| private static void assertMatches(String output, String pattern) { |
| assertTrue("Pattern " + pattern + " did not match output : " + output, output.matches(pattern)); |
| } |
| |
| private static void assertNotContains(String output, String subsequence) { |
| assertFalse("Expected '" + subsequence + "' would not occur in output : " + output, |
| output.contains(subsequence)); |
| } |
| |
| @Test |
| public void testSummaries() throws Exception { |
| ts.exec("createtable summary"); |
| ts.exec("config -t summary -s table.summarizer.del=" + DeletesSummarizer.class.getName()); |
| ts.exec("config -t summary -s table.summarizer.fam=" + FamilySummarizer.class.getName()); |
| |
| ts.exec("addsplits -t summary r1 r2"); |
| ts.exec("insert r1 f1 q1 v1"); |
| ts.exec("insert r2 f2 q1 v3"); |
| ts.exec("insert r2 f2 q2 v4"); |
| ts.exec("insert r3 f3 q1 v5"); |
| ts.exec("insert r3 f3 q2 v6"); |
| ts.exec("insert r3 f3 q3 v7"); |
| ts.exec("flush -t summary -w"); |
| |
| String output = ts.exec("summaries"); |
| assertMatches(output, "(?sm).*^.*deletes\\s+=\\s+0.*$.*"); |
| assertMatches(output, "(?sm).*^.*total\\s+=\\s+6.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+3.*$.*"); |
| |
| ts.exec("delete r1 f1 q2"); |
| ts.exec("delete r2 f2 q1"); |
| ts.exec("flush -t summary -w"); |
| |
| output = ts.exec("summaries"); |
| assertMatches(output, "(?sm).*^.*deletes\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*total\\s+=\\s+8.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+3.*$.*"); |
| |
| output = ts.exec("summaries -e r2"); |
| assertMatches(output, "(?sm).*^.*deletes\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*total\\s+=\\s+5.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertNotContains(output, "c:f3"); |
| |
| output = ts.exec("summaries -b r2"); |
| assertMatches(output, "(?sm).*^.*deletes\\s+=\\s+0.*$.*"); |
| assertMatches(output, "(?sm).*^.*total\\s+=\\s+3.*$.*"); |
| assertNotContains(output, "c:f1"); |
| assertNotContains(output, "c:f2"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+3.*$.*"); |
| |
| output = ts.exec("summaries -b r1 -e r2"); |
| assertMatches(output, "(?sm).*^.*deletes\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*total\\s+=\\s+3.*$.*"); |
| assertNotContains(output, "c:f1"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertNotContains(output, "c:f3"); |
| |
| output = ts.exec("summaries -sr .*Family.*"); |
| assertNotContains(output, "deletes "); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+3.*$.*"); |
| |
| output = ts.exec("summaries -b r1 -e r2 -sr .*Family.*"); |
| assertNotContains(output, "deletes "); |
| assertNotContains(output, "c:f1"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertNotContains(output, "c:f3"); |
| } |
| |
| @Test |
| public void testSummarySelection() throws Exception { |
| ts.exec("createtable summary2"); |
| // will create a few files and do not want them compacted |
| ts.exec("config -t summary2 -s " + Property.TABLE_MAJC_RATIO + "=10"); |
| |
| ts.exec("insert r1 f1 q1 v1"); |
| ts.exec("insert r2 f2 q1 v2"); |
| ts.exec("flush -t summary2 -w"); |
| |
| ts.exec("config -t summary2 -s table.summarizer.fam=" + FamilySummarizer.class.getName()); |
| |
| ts.exec("insert r1 f2 q1 v3"); |
| ts.exec("insert r3 f3 q1 v4"); |
| ts.exec("flush -t summary2 -w"); |
| |
| String output = ts.exec("summaries"); |
| assertNotContains(output, "c:f1"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+1.*$.*"); |
| // check that there are two files, with one missing summary info |
| assertMatches(output, "(?sm).*^.*total[:]2[,]\\s+missing[:]1[,]\\s+extra[:]0.*$.*"); |
| |
| // compact only the file missing summary info |
| ts.exec("compact -t summary2 --sf-no-summary -w"); |
| output = ts.exec("summaries"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+1.*$.*"); |
| // check that there are two files, with none missing summary info |
| assertMatches(output, "(?sm).*^.*total[:]2[,]\\s+missing[:]0[,]\\s+extra[:]0.*$.*"); |
| |
| // create a situation where files has summary data outside of tablet |
| ts.exec("addsplits -t summary2 r2"); |
| output = ts.exec("summaries -e r2"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f3\\s+=\\s+1.*$.*"); |
| // check that there are two files, with one having extra summary info |
| assertMatches(output, "(?sm).*^.*total[:]2[,]\\s+missing[:]0[,]\\s+extra[:]1.*$.*"); |
| |
| // compact only the files with extra summary info |
| ts.exec("compact -t summary2 --sf-extra-summary -w"); |
| output = ts.exec("summaries -e r2"); |
| assertMatches(output, "(?sm).*^.*c:f1\\s+=\\s+1.*$.*"); |
| assertMatches(output, "(?sm).*^.*c:f2\\s+=\\s+2.*$.*"); |
| assertNotContains(output, "c:f3"); |
| // check that there are two files, with none having extra summary info |
| assertMatches(output, "(?sm).*^.*total[:]2[,]\\s+missing[:]0[,]\\s+extra[:]0.*$.*"); |
| } |
| |
| @Test |
| public void testCreateTableWithLocalityGroups() throws Exception { |
| final String table = name.getMethodName(); |
| ts.exec("createtable " + table + " -l locg1=fam1,fam2", true); |
| try (AccumuloClient accumuloClient = Accumulo.newClient().from(getClientProps()).build()) { |
| Map<String,Set<Text>> lMap = accumuloClient.tableOperations().getLocalityGroups(table); |
| Set<Text> expectedColFams = new HashSet<>(Arrays.asList(new Text("fam1"), new Text("fam2"))); |
| for (Entry<String,Set<Text>> entry : lMap.entrySet()) { |
| assertEquals("locg1", entry.getKey()); |
| assertTrue(entry.getValue().containsAll(expectedColFams)); |
| } |
| ts.exec("deletetable -f " + table); |
| } |
| } |
| |
| /** |
| * Due to the existing complexity of the createtable command, the createtable help only displays |
| * an example of setting one locality group. It is possible to set multiple groups if needed. This |
| * test verifies that capability. |
| */ |
| @Test |
| public void testCreateTableWithMultipleLocalityGroups() throws Exception { |
| final String table = name.getMethodName(); |
| ts.exec("createtable " + table + " -l locg1=fam1,fam2 locg2=colfam1", true); |
| try (AccumuloClient accumuloClient = Accumulo.newClient().from(getClientProps()).build()) { |
| Map<String,Set<Text>> lMap = accumuloClient.tableOperations().getLocalityGroups(table); |
| assertTrue(lMap.keySet().contains("locg1")); |
| assertTrue(lMap.keySet().contains("locg2")); |
| Set<Text> expectedColFams1 = new HashSet<>(Arrays.asList(new Text("fam1"), new Text("fam2"))); |
| Set<Text> expectedColFams2 = new HashSet<>(Arrays.asList(new Text("colfam1"))); |
| assertTrue(lMap.get("locg1").containsAll(expectedColFams1)); |
| assertTrue(lMap.get("locg2").containsAll(expectedColFams2)); |
| ts.exec("deletetable -f " + table); |
| } |
| } |
| |
| @Test |
| public void testCreateTableWithLocalityGroupsBadArguments() throws IOException { |
| final String table = name.getMethodName(); |
| ts.exec("createtable " + table + " -l locg1 fam1,fam2", false); |
| ts.exec("createtable " + table + "-l", false); |
| ts.exec("createtable " + table + " -l locg1 = fam1,fam2", false); |
| ts.exec("createtable " + table + " -l locg1=fam1 ,fam2", false); |
| ts.exec("createtable " + table + " -l locg1=fam1,fam2 locg1=fam3,fam4", false); |
| ts.exec("createtable " + table + " -l locg1=fam1,fam2 locg2=fam1", false); |
| ts.exec("createtable " + table + " -l locg1", false); |
| ts.exec("createtable " + table + " group=fam1", false); |
| ts.exec("createtable " + table + "-l fam1,fam2", false); |
| } |
| |
| @Test |
| public void testCreateTableWithIterators() throws Exception { |
| final String tmpTable = "tmpTable"; |
| final String table = name.getMethodName(); |
| |
| // create iterator profile |
| // Will use tmpTable for creating profile since setshelliter is requiring a table |
| // even though its command line help indicates that it is optional. Likely due to |
| // the fact that setshelliter extends setiter, which does require a table argument. |
| ts.exec("createtable " + tmpTable, true); |
| String output = ts.exec("tables"); |
| assertTrue(output.contains(tmpTable)); |
| |
| ts.input.set("\n5000\n\n"); |
| ts.exec("setshelliter -n itname -p 10 -pn profile1 -ageoff", true); |
| output = ts.exec("listshelliter"); |
| assertTrue(output.contains("Profile : profile1")); |
| |
| // create table making use of the iterator profile |
| ts.exec("createtable " + table + " -i profile1:scan,minc", true); |
| ts.exec("insert foo a b c", true); |
| ts.exec("scan", true, "foo a:b [] c"); |
| ts.exec("sleep 6", true); |
| ts.exec("scan", true, "", true); |
| ts.exec("deletetable -f " + table); |
| ts.exec("deletetable -f " + tmpTable); |
| } |
| |
| /** |
| * Due to the existing complexity of the createtable command, the createtable help only displays |
| * an example of setting one iterator upon table creation. It is possible to set multiple if |
| * needed. This test verifies that capability. |
| */ |
| @Test |
| public void testCreateTableWithMultipleIterators() throws Exception { |
| final String tmpTable = "tmpTable"; |
| final String table = name.getMethodName(); |
| |
| // create iterator profile |
| // Will use tmpTable for creating profile since setshelliter is requiring a table |
| // even though its command line help indicates that it is optional. Likely due to |
| // the fact that setshelliter extends setiter, which does require a table argument. |
| ts.exec("createtable " + tmpTable, true); |
| String output = ts.exec("tables"); |
| assertTrue(output.contains(tmpTable)); |
| |
| ts.input.set("\n5000\n\n"); |
| ts.exec("setshelliter -n itname -p 10 -pn profile1 -ageoff", true); |
| output = ts.exec("listshelliter"); |
| assertTrue(output.contains("Profile : profile1")); |
| |
| ts.input.set("2\n"); |
| ts.exec("setshelliter -n iter2 -p 11 -pn profile2 -vers", true); |
| output = ts.exec("listshelliter"); |
| assertTrue(output.contains("Profile : profile2")); |
| |
| // create table making use of the iterator profiles |
| ts.exec("createtable " + table + " -i profile1:scan,minc profile2:all ", true); |
| ts.exec("insert foo a b c", true); |
| ts.exec("scan", true, "foo a:b [] c"); |
| ts.exec("sleep 6", true); |
| ts.exec("scan", true, "", true); |
| output = ts.exec("listiter -t " + table + " -all"); |
| assertTrue(output.contains("Iterator itname, scan scope options")); |
| assertTrue(output.contains("Iterator itname, minc scope options")); |
| assertFalse(output.contains("Iterator itname, majc scope options")); |
| assertTrue(output.contains("Iterator iter2, scan scope options")); |
| assertTrue(output.contains("Iterator iter2, minc scope options")); |
| assertTrue(output.contains("Iterator iter2, majc scope options")); |
| ts.exec("deletetable -f " + table); |
| ts.exec("deletetable -f " + tmpTable); |
| } |
| |
| @Test |
| public void testCreateTableWithIteratorsBadArguments() throws IOException { |
| final String tmpTable = "tmpTable"; |
| final String table = name.getMethodName(); |
| ts.exec("createtable " + tmpTable, true); |
| String output = ts.exec("tables"); |
| assertTrue(output.contains(tmpTable)); |
| ts.input.set("\n5000\n\n"); |
| ts.exec("setshelliter -n itname -p 10 -pn profile1 -ageoff", true); |
| output = ts.exec("listshelliter"); |
| assertTrue(output.contains("Profile : profile1")); |
| // test various bad argument calls |
| ts.exec("createtable " + table + " -i noprofile:scan,minc", false); |
| ts.exec("createtable " + table + " -i profile1:scan,minc,all,majc", false); |
| ts.exec("createtable " + table + " -i profile1:scan,all,majc", false); |
| ts.exec("createtable " + table + " -i profile1:scan,min,majc", false); |
| ts.exec("createtable " + table + " -i profile1:scan,max,all", false); |
| ts.exec("createtable " + table + " -i profile1:", false); |
| ts.exec("createtable " + table + " -i profile1: ", false); |
| ts.exec("createtable " + table + " -i profile1:-scan", false); |
| ts.exec("createtable " + table + " profile1:majc", false); |
| ts.exec("createtable " + table + " -i profile1: all", false); |
| ts.exec("createtable " + table + " -i profile1: All", false); |
| ts.exec("createtable " + table + " -i profile1: scan", false); |
| ts.exec("createtable " + table + " -i profile1:minc scan", false); |
| ts.exec("createtable " + table + " -i profile1:minc,Scan", false); |
| ts.exec("createtable " + table + " -i profile1:minc, scan", false); |
| ts.exec("createtable " + table + " -i profile1:minc,,scan", false); |
| ts.exec("createtable " + table + " -i profile1:minc,minc", false); |
| ts.exec("createtable " + table + " -i profile1:minc,Minc", false); |
| ts.exec("createtable " + table + " -i profile1:minc, ,scan", false); |
| ts.exec("createtable " + table + "-i", false); |
| ts.exec("createtable " + table + "-i ", false); |
| ts.exec("deletetable -f " + tmpTable); |
| } |
| |
| /** |
| * Verify that table can be created in offline status and then be brought online. |
| */ |
| @Test |
| public void testCreateTableOffline() throws IOException { |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -o", true); |
| String output = ts.exec("tables"); |
| assertTrue(output.contains(tableName)); |
| output = ts.exec("scan -t " + tableName, false, "is offline", true); |
| assertTrue(output.contains("TableOfflineException")); |
| ts.exec("table " + tableName, true); |
| ts.exec("online", true); |
| ts.exec("scan", true); |
| ts.exec("deletetable -f " + tableName, true); |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and un-encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile1() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 1000, 12, false, false, true, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, unsorted and un-encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile2() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 300, 12, false, false, false, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile3() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 23, false, true, true, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and un-encoded with a blank line and no repeats. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile4() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 31, false, false, true, true, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and un-encoded with a blank line and no repeats. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile5() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 32, false, false, true, false, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, unsorted and un-encoded with a blank line and repeats. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile6() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 12, false, false, false, true, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with a blank line and repeats. |
| */ |
| @Test |
| public void testCreateTableWithSplitsFile7() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 12, false, false, true, true, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits file will be empty. |
| */ |
| @Test(expected = org.apache.accumulo.core.client.TableNotFoundException.class) |
| public void testCreateTableWithEmptySplitFile() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 0, 0, false, false, false, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, false); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table that used splits from another table. |
| */ |
| @Test |
| public void testCreateTableWithCopySplitsFromOtherTable() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| // create a table and add some splits |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| final String tableName1 = name.getMethodName() + "_table1"; |
| ts.exec("createtable " + tableName1, true); |
| String output = ts.exec("tables", true); |
| assertTrue(output.contains(tableName1)); |
| ts.exec("table " + tableName1, true); |
| // add splits to this table using the addsplits command. |
| List<Text> splits = new ArrayList<>(); |
| splits.add(new Text("ccccc")); |
| splits.add(new Text("fffff")); |
| splits.add(new Text("mmmmm")); |
| splits.add(new Text("sssss")); |
| ts.exec("addsplits " + splits.get(0) + " " + splits.get(1) + " " + splits.get(2) + " " |
| + splits.get(3), true); |
| // Now create a table that will used the previous tables splits and create them at table |
| // creation |
| final String tableName2 = name.getMethodName() + "_table2"; |
| ts.exec("createtable " + tableName2 + " --copy-splits " + tableName1, true); |
| ts.exec("table " + tableName1, true); |
| String tablesOutput = ts.exec("tables", true); |
| assertTrue(tablesOutput.contains(tableName2)); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName2); |
| assertEquals(new TreeSet<>(splits), new TreeSet<>(createdSplits)); |
| ts.exec("deletetable -f " + tableName1, true); |
| ts.exec("deletetable -f " + tableName2, true); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile1() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 200, 12, true, true, true, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, unsorted and encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile2() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 300, 12, true, true, false, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with no repeats or blank lines. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile3() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 23, true, true, true, false, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with a blank line and no repeats. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile4() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 31, true, true, true, true, false); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with a blank line and no repeats. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile5() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 32, true, true, true, false, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, unsorted and encoded with a blank line and repeats. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile6() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 12, true, true, false, true, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| /** |
| * Use shell to create a table with a supplied file containing splits. |
| * |
| * The splits will be contained in a file, sorted and encoded with a blank line and repeats. |
| */ |
| @Test |
| public void testCreateTableWithBinarySplitsFile7() |
| throws IOException, AccumuloSecurityException, TableNotFoundException, AccumuloException { |
| String splitsFile = null; |
| try (AccumuloClient client = Accumulo.newClient().from(getClientProps()).build()) { |
| splitsFile = System.getProperty("user.dir") + "/target/splitFile"; |
| generateSplitsFile(splitsFile, 100, 12, true, true, true, true, true); |
| SortedSet<Text> expectedSplits = readSplitsFromFile(splitsFile, false); |
| final String tableName = name.getMethodName() + "_table"; |
| ts.exec("createtable " + tableName + " -sf " + splitsFile, true); |
| Collection<Text> createdSplits = client.tableOperations().listSplits(tableName); |
| assertEquals(expectedSplits, new TreeSet<>(createdSplits)); |
| } finally { |
| Files.delete(Paths.get(splitsFile)); |
| } |
| } |
| |
| private SortedSet<Text> readSplitsFromFile(final String splitsFile, boolean decode) |
| throws IOException { |
| SortedSet<Text> splits = new TreeSet<>(); |
| try (BufferedReader reader = newBufferedReader(Paths.get(splitsFile))) { |
| String split; |
| while ((split = reader.readLine()) != null) { |
| Text unencodedString = decode(split, decode); |
| if (unencodedString != null) |
| splits.add(unencodedString); |
| } |
| } |
| return splits; |
| } |
| |
| private void generateSplitsFile(final String splitsFile, final int numItems, final int len, |
| final boolean binarySplits, final boolean encoded, final boolean sort, |
| final boolean addBlankLine, final boolean repeat) throws IOException { |
| |
| java.nio.file.Path splitsPath = java.nio.file.Paths.get(splitsFile); |
| int insertAt = (len % 2 == 0) ? len / 2 : (len + 1) / 2; |
| Collection<Text> sortedSplits = null; |
| Collection<Text> randomSplits = null; |
| |
| if (binarySplits) |
| randomSplits = generateBinarySplits(numItems, len); |
| else |
| randomSplits = generateNonBinarySplits(numItems, len); |
| |
| if (sort) |
| sortedSplits = new TreeSet<>(randomSplits); |
| |
| try (BufferedWriter writer = Files.newBufferedWriter(splitsPath, Charset.forName("UTF-8"))) { |
| int cnt = 0; |
| Collection<Text> splits; |
| if (sort) |
| splits = sortedSplits; |
| else |
| splits = randomSplits; |
| |
| for (Text text : splits) { |
| if (addBlankLine && cnt++ == insertAt) |
| writer.write('\n'); |
| writer.write(encode(text, encoded) + '\n'); |
| if (repeat) |
| writer.write(encode(text, encoded) + '\n'); |
| } |
| } |
| } |
| |
| private Collection<Text> generateNonBinarySplits(final int numItems, final int len) { |
| Set<Text> splits = new HashSet<>(); |
| for (int i = 0; i < numItems; i++) { |
| splits.add(getRandomText(len)); |
| } |
| return splits; |
| } |
| |
| @SuppressFBWarnings(value = "PREDICTABLE_RANDOM", |
| justification = "predictable random is okay for testing") |
| private Collection<Text> generateBinarySplits(final int numItems, final int len) { |
| Set<Text> splits = new HashSet<>(); |
| Random rand = new Random(); |
| for (int i = 0; i < numItems; i++) { |
| byte[] split = new byte[len]; |
| rand.nextBytes(split); |
| splits.add(new Text(split)); |
| } |
| return splits; |
| } |
| |
| private Text getRandomText(final int len) { |
| int desiredLen = len; |
| if (len > 32) |
| desiredLen = 32; |
| return new Text( |
| String.valueOf(UUID.randomUUID()).replaceAll("-", "").substring(0, desiredLen - 1)); |
| } |
| |
| private static String encode(final Text text, final boolean encode) { |
| if (StringUtils.isBlank(text.toString())) |
| return null; |
| return encode ? Base64.getEncoder().encodeToString(TextUtil.getBytes(text)) : text.toString(); |
| } |
| |
| private Text decode(final String text, final boolean decode) { |
| if (StringUtils.isBlank(text)) |
| return null; |
| return decode ? new Text(Base64.getDecoder().decode(text)) : new Text(text); |
| } |
| } |