/*
* 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.functional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map.Entry;
import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.util.CachedConfiguration;
import org.apache.accumulo.fate.util.UtilWaitThread;
import org.apache.accumulo.harness.AccumuloClusterHarness;
import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.hamcrest.CoreMatchers;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
public class ScannerContextIT extends AccumuloClusterHarness {
private static final String CONTEXT = ScannerContextIT.class.getSimpleName();
private static final String CONTEXT_PROPERTY = Property.VFS_CONTEXT_CLASSPATH_PROPERTY + CONTEXT;
private static final String CONTEXT_DIR = "file://" + System.getProperty("user.dir") + "/target";
private static final String CONTEXT_CLASSPATH = CONTEXT_DIR + "/Test.jar";
private static int ITERATIONS = 10;
private static final long WAIT = 7000;
private FileSystem fs;
@Override
protected int defaultTimeoutSeconds() {
return 2 * 60;
}
@Before
public void checkCluster() throws Exception {
Assume.assumeThat(getClusterType(), CoreMatchers.is(ClusterType.MINI));
MiniAccumuloClusterImpl.class.cast(getCluster());
fs = FileSystem.get(CachedConfiguration.getInstance());
}
private Path copyTestIteratorsJarToTmp() 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(CONTEXT_DIR + "/Test.jar");
fs.copyFromLocalFile(jarPath, dstPath);
// Sleep to ensure jar change gets picked up
UtilWaitThread.sleep(WAIT);
return dstPath;
}
@Test
public void test() throws Exception {
Path dstPath = copyTestIteratorsJarToTmp();
try {
Connector c = getConnector();
// Set the classloader context property on the table to point to the test iterators jar file.
c.instanceOperations().setProperty(CONTEXT_PROPERTY, CONTEXT_CLASSPATH);
// Insert rows with the word "Test" in the value.
String tableName = getUniqueNames(1)[0];
c.tableOperations().create(tableName);
BatchWriter bw = c.createBatchWriter(tableName, new BatchWriterConfig());
for (int i = 0; i < ITERATIONS; i++) {
Mutation m = new Mutation("row" + i);
m.put("cf", "col1", "Test");
bw.addMutation(m);
}
bw.close();
// Ensure that we can get the data back
scanCheck(c, tableName, null, null, "Test");
batchCheck(c, tableName, null, null, "Test");
// This iterator is in the test iterators jar file
IteratorSetting cfg = new IteratorSetting(21, "reverse", "org.apache.accumulo.test.functional.ValueReversingIterator");
// Check that ValueReversingIterator is not already on the classpath by not setting the context. This should fail.
try {
scanCheck(c, tableName, cfg, null, "tseT");
fail("This should have failed because context was not set");
} catch (Exception e) {
// Do nothing, this should fail as the classloader context is not set.
}
try {
batchCheck(c, tableName, cfg, null, "tseT");
fail("This should have failed because context was not set");
} catch (Exception e) {
// Do nothing, this should fail as the classloader context is not set.
}
// Ensure that the value is reversed using the iterator config and classloader context
scanCheck(c, tableName, cfg, CONTEXT, "tseT");
batchCheck(c, tableName, cfg, CONTEXT, "tseT");
} finally {
// Delete file in tmp
fs.delete(dstPath, true);
}
}
@Test
public void testScanContextOverridesTableContext() throws Exception {
Path dstPath = copyTestIteratorsJarToTmp();
try {
Connector c = getConnector();
// Create two contexts FOO and ScanContextIT. The FOO context will point to a classpath
// that contains nothing. The ScanContextIT context will point to the test iterators jar
String tableContext = "FOO";
String tableContextProperty = Property.VFS_CONTEXT_CLASSPATH_PROPERTY + tableContext;
String tableContextDir = "file://" + System.getProperty("user.dir") + "/target";
String tableContextClasspath = tableContextDir + "/TestFoo.jar";
// Define both contexts
c.instanceOperations().setProperty(tableContextProperty, tableContextClasspath);
c.instanceOperations().setProperty(CONTEXT_PROPERTY, CONTEXT_CLASSPATH);
String tableName = getUniqueNames(1)[0];
c.tableOperations().create(tableName);
// Set the FOO context on the table
c.tableOperations().setProperty(tableName, Property.TABLE_CLASSPATH.getKey(), tableContext);
BatchWriter bw = c.createBatchWriter(tableName, new BatchWriterConfig());
for (int i = 0; i < ITERATIONS; i++) {
Mutation m = new Mutation("row" + i);
m.put("cf", "col1", "Test");
bw.addMutation(m);
}
bw.close();
scanCheck(c, tableName, null, null, "Test");
batchCheck(c, tableName, null, null, "Test");
// This iterator is in the test iterators jar file
IteratorSetting cfg = new IteratorSetting(21, "reverse", "org.apache.accumulo.test.functional.ValueReversingIterator");
// Check that ValueReversingIterator is not already on the classpath by not setting the context. This should fail.
try {
scanCheck(c, tableName, cfg, null, "tseT");
fail("This should have failed because context was not set");
} catch (Exception e) {
// Do nothing, this should fail as the classloader context is not set.
}
try {
batchCheck(c, tableName, cfg, null, "tseT");
fail("This should have failed because context was not set");
} catch (Exception e) {
// Do nothing, this should fail as the classloader context is not set.
}
// Ensure that the value is reversed using the iterator config and classloader context
scanCheck(c, tableName, cfg, CONTEXT, "tseT");
batchCheck(c, tableName, cfg, CONTEXT, "tseT");
} finally {
// Delete file in tmp
fs.delete(dstPath, true);
}
}
@Test
public void testOneScannerDoesntInterfereWithAnother() throws Exception {
Path dstPath = copyTestIteratorsJarToTmp();
try {
Connector c = getConnector();
// Set the classloader context property on the table to point to the test iterators jar file.
c.instanceOperations().setProperty(CONTEXT_PROPERTY, CONTEXT_CLASSPATH);
// Insert rows with the word "Test" in the value.
String tableName = getUniqueNames(1)[0];
c.tableOperations().create(tableName);
BatchWriter bw = c.createBatchWriter(tableName, new BatchWriterConfig());
for (int i = 0; i < ITERATIONS; i++) {
Mutation m = new Mutation("row" + i);
m.put("cf", "col1", "Test");
bw.addMutation(m);
}
bw.close();
Scanner one = c.createScanner(tableName, Authorizations.EMPTY);
Scanner two = c.createScanner(tableName, Authorizations.EMPTY);
IteratorSetting cfg = new IteratorSetting(21, "reverse", "org.apache.accumulo.test.functional.ValueReversingIterator");
one.addScanIterator(cfg);
one.setClassLoaderContext(CONTEXT);
Iterator<Entry<Key,Value>> iterator = one.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator.hasNext());
Entry<Key,Value> next = iterator.next();
assertEquals("tseT", next.getValue().toString());
}
Iterator<Entry<Key,Value>> iterator2 = two.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator2.hasNext());
Entry<Key,Value> next = iterator2.next();
assertEquals("Test", next.getValue().toString());
}
} finally {
// Delete file in tmp
fs.delete(dstPath, true);
}
}
@Test
public void testClearContext() throws Exception {
Path dstPath = copyTestIteratorsJarToTmp();
try {
Connector c = getConnector();
// Set the classloader context property on the table to point to the test iterators jar file.
c.instanceOperations().setProperty(CONTEXT_PROPERTY, CONTEXT_CLASSPATH);
// Insert rows with the word "Test" in the value.
String tableName = getUniqueNames(1)[0];
c.tableOperations().create(tableName);
BatchWriter bw = c.createBatchWriter(tableName, new BatchWriterConfig());
for (int i = 0; i < ITERATIONS; i++) {
Mutation m = new Mutation("row" + i);
m.put("cf", "col1", "Test");
bw.addMutation(m);
}
bw.close();
Scanner one = c.createScanner(tableName, Authorizations.EMPTY);
IteratorSetting cfg = new IteratorSetting(21, "reverse", "org.apache.accumulo.test.functional.ValueReversingIterator");
one.addScanIterator(cfg);
one.setClassLoaderContext(CONTEXT);
Iterator<Entry<Key,Value>> iterator = one.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator.hasNext());
Entry<Key,Value> next = iterator.next();
assertEquals("tseT", next.getValue().toString());
}
one.removeScanIterator("reverse");
one.clearClassLoaderContext();
iterator = one.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator.hasNext());
Entry<Key,Value> next = iterator.next();
assertEquals("Test", next.getValue().toString());
}
} finally {
// Delete file in tmp
fs.delete(dstPath, true);
}
}
private void scanCheck(Connector c, String tableName, IteratorSetting cfg, String context, String expected) throws Exception {
Scanner bs = c.createScanner(tableName, Authorizations.EMPTY);
if (null != context) {
bs.setClassLoaderContext(context);
}
if (null != cfg) {
bs.addScanIterator(cfg);
}
Iterator<Entry<Key,Value>> iterator = bs.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator.hasNext());
Entry<Key,Value> next = iterator.next();
assertEquals(expected, next.getValue().toString());
}
assertFalse(iterator.hasNext());
}
private void batchCheck(Connector c, String tableName, IteratorSetting cfg, String context, String expected) throws Exception {
BatchScanner bs = c.createBatchScanner(tableName, Authorizations.EMPTY, 1);
bs.setRanges(Collections.singleton(new Range()));
try {
if (null != context) {
bs.setClassLoaderContext(context);
}
if (null != cfg) {
bs.addScanIterator(cfg);
}
Iterator<Entry<Key,Value>> iterator = bs.iterator();
for (int i = 0; i < ITERATIONS; i++) {
assertTrue(iterator.hasNext());
Entry<Key,Value> next = iterator.next();
assertEquals(expected, next.getValue().toString());
}
assertFalse(iterator.hasNext());
} finally {
bs.close();
}
}
}