/**
*
* 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.hadoop.hbase;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableMap;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.InternalScanner;
import org.apache.hadoop.hbase.util.*;
import org.apache.hadoop.hdfs.MiniDFSCluster;
/**
* Abstract HBase test class. Initializes a few things that can come in handly
* like an HBaseConfiguration and filesystem.
* @deprecated Write junit4 unit tests using {@link HBaseTestingUtility}
*/
public abstract class HBaseTestCase extends TestCase {
private static final Log LOG = LogFactory.getLog(HBaseTestCase.class);
protected final static byte [] fam1 = Bytes.toBytes("colfamily11");
protected final static byte [] fam2 = Bytes.toBytes("colfamily21");
protected final static byte [] fam3 = Bytes.toBytes("colfamily31");
protected static final byte [][] COLUMNS = {fam1, fam2, fam3};
private boolean localfs = false;
protected static Path testDir = null;
protected FileSystem fs = null;
protected HRegion root = null;
protected HRegion meta = null;
protected static final char FIRST_CHAR = 'a';
protected static final char LAST_CHAR = 'z';
protected static final String PUNCTUATION = "~`@#$%^&*()-_+=:;',.<>/?[]{}|";
protected static final byte [] START_KEY_BYTES = {FIRST_CHAR, FIRST_CHAR, FIRST_CHAR};
protected String START_KEY;
protected static final int MAXVERSIONS = 3;
protected final HBaseTestingUtility testUtil = new HBaseTestingUtility();
public volatile Configuration conf;
/** constructor */
public HBaseTestCase() {
super();
init();
}
/**
* @param name
*/
public HBaseTestCase(String name) {
super(name);
init();
}
private void init() {
conf = HBaseConfiguration.create();
START_KEY = new String(START_KEY_BYTES, HConstants.UTF8_CHARSET);
}
/**
* Note that this method must be called after the mini hdfs cluster has
* started or we end up with a local file system.
*/
@Override
protected void setUp() throws Exception {
super.setUp();
localfs =
(conf.get("fs.defaultFS", "file:///").compareTo("file:///") == 0);
if (fs == null) {
this.fs = FileSystem.get(conf);
}
try {
if (localfs) {
this.testDir = getUnitTestdir(getName());
if (fs.exists(testDir)) {
fs.delete(testDir, true);
}
} else {
this.testDir =
this.fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR)));
}
} catch (Exception e) {
LOG.fatal("error during setup", e);
throw e;
}
}
@Override
protected void tearDown() throws Exception {
try {
if (localfs) {
if (this.fs.exists(testDir)) {
this.fs.delete(testDir, true);
}
}
} catch (Exception e) {
LOG.fatal("error during tear down", e);
}
super.tearDown();
}
/**
* @see HBaseTestingUtility#getBaseTestDir
* @param testName
* @return directory to use for this test
*/
protected Path getUnitTestdir(String testName) {
return testUtil.getDataTestDir(testName);
}
/**
* You must call close on the returned region and then close on the log file
* it created. Do {@link HRegion#close()} followed by {@link HRegion#getLog()}
* and on it call close.
* @param desc
* @param startKey
* @param endKey
* @return An {@link HRegion}
* @throws IOException
*/
public HRegion createNewHRegion(HTableDescriptor desc, byte [] startKey,
byte [] endKey)
throws IOException {
return createNewHRegion(desc, startKey, endKey, this.conf);
}
public HRegion createNewHRegion(HTableDescriptor desc, byte [] startKey,
byte [] endKey, Configuration conf)
throws IOException {
HRegionInfo hri = new HRegionInfo(desc.getName(), startKey, endKey);
return HRegion.createHRegion(hri, testDir, conf, desc);
}
protected HRegion openClosedRegion(final HRegion closedRegion)
throws IOException {
HRegion r = new HRegion(closedRegion);
r.initialize();
return r;
}
/**
* Create a table of name <code>name</code> with {@link COLUMNS} for
* families.
* @param name Name to give table.
* @return Column descriptor.
*/
protected HTableDescriptor createTableDescriptor(final String name) {
return createTableDescriptor(name, MAXVERSIONS);
}
/**
* Create a table of name <code>name</code> with {@link COLUMNS} for
* families.
* @param name Name to give table.
* @param versions How many versions to allow per column.
* @return Column descriptor.
*/
protected HTableDescriptor createTableDescriptor(final String name,
final int versions) {
return createTableDescriptor(name, HColumnDescriptor.DEFAULT_MIN_VERSIONS,
versions, HConstants.FOREVER, HColumnDescriptor.DEFAULT_KEEP_DELETED);
}
/**
* Create a table of name <code>name</code> with {@link COLUMNS} for
* families.
* @param name Name to give table.
* @param versions How many versions to allow per column.
* @return Column descriptor.
*/
protected HTableDescriptor createTableDescriptor(final String name,
final int minVersions, final int versions, final int ttl, boolean keepDeleted) {
HTableDescriptor htd = new HTableDescriptor(name);
for (byte[] cfName : new byte[][]{ fam1, fam2, fam3 }) {
htd.addFamily(new HColumnDescriptor(cfName)
.setMinVersions(minVersions)
.setMaxVersions(versions)
.setKeepDeletedCells(keepDeleted)
.setBlockCacheEnabled(false)
.setTimeToLive(ttl)
);
}
return htd;
}
/**
* Add content to region <code>r</code> on the passed column
* <code>column</code>.
* Adds data of the from 'aaa', 'aab', etc where key and value are the same.
* @param r
* @param columnFamily
* @param column
* @throws IOException
* @return count of what we added.
*/
public static long addContent(final HRegion r, final byte [] columnFamily, final byte[] column)
throws IOException {
byte [] startKey = r.getRegionInfo().getStartKey();
byte [] endKey = r.getRegionInfo().getEndKey();
byte [] startKeyBytes = startKey;
if (startKeyBytes == null || startKeyBytes.length == 0) {
startKeyBytes = START_KEY_BYTES;
}
return addContent(new HRegionIncommon(r), Bytes.toString(columnFamily), Bytes.toString(column),
startKeyBytes, endKey, -1);
}
/**
* Add content to region <code>r</code> on the passed column
* <code>column</code>.
* Adds data of the from 'aaa', 'aab', etc where key and value are the same.
* @param r
* @param columnFamily
* @throws IOException
* @return count of what we added.
*/
protected static long addContent(final HRegion r, final byte [] columnFamily)
throws IOException {
return addContent(r, columnFamily, null);
}
/**
* Add content to region <code>r</code> on the passed column
* <code>column</code>.
* Adds data of the from 'aaa', 'aab', etc where key and value are the same.
* @param updater An instance of {@link Incommon}.
* @param columnFamily
* @throws IOException
* @return count of what we added.
*/
protected static long addContent(final Incommon updater,
final String columnFamily) throws IOException {
return addContent(updater, columnFamily, START_KEY_BYTES, null);
}
protected static long addContent(final Incommon updater, final String family,
final String column) throws IOException {
return addContent(updater, family, column, START_KEY_BYTES, null);
}
/**
* Add content to region <code>r</code> on the passed column
* <code>column</code>.
* Adds data of the from 'aaa', 'aab', etc where key and value are the same.
* @param updater An instance of {@link Incommon}.
* @param columnFamily
* @param startKeyBytes Where to start the rows inserted
* @param endKey Where to stop inserting rows.
* @return count of what we added.
* @throws IOException
*/
protected static long addContent(final Incommon updater, final String columnFamily,
final byte [] startKeyBytes, final byte [] endKey)
throws IOException {
return addContent(updater, columnFamily, null, startKeyBytes, endKey, -1);
}
protected static long addContent(final Incommon updater, final String family,
final String column, final byte [] startKeyBytes,
final byte [] endKey) throws IOException {
return addContent(updater, family, column, startKeyBytes, endKey, -1);
}
/**
* Add content to region <code>r</code> on the passed column
* <code>column</code>.
* Adds data of the from 'aaa', 'aab', etc where key and value are the same.
* @param updater An instance of {@link Incommon}.
* @param column
* @param startKeyBytes Where to start the rows inserted
* @param endKey Where to stop inserting rows.
* @param ts Timestamp to write the content with.
* @return count of what we added.
* @throws IOException
*/
protected static long addContent(final Incommon updater,
final String columnFamily,
final String column,
final byte [] startKeyBytes, final byte [] endKey, final long ts)
throws IOException {
long count = 0;
// Add rows of three characters. The first character starts with the
// 'a' character and runs up to 'z'. Per first character, we run the
// second character over same range. And same for the third so rows
// (and values) look like this: 'aaa', 'aab', 'aac', etc.
char secondCharStart = (char)startKeyBytes[1];
char thirdCharStart = (char)startKeyBytes[2];
EXIT: for (char c = (char)startKeyBytes[0]; c <= LAST_CHAR; c++) {
for (char d = secondCharStart; d <= LAST_CHAR; d++) {
for (char e = thirdCharStart; e <= LAST_CHAR; e++) {
byte [] t = new byte [] {(byte)c, (byte)d, (byte)e};
if (endKey != null && endKey.length > 0
&& Bytes.compareTo(endKey, t) <= 0) {
break EXIT;
}
try {
Put put;
if(ts != -1) {
put = new Put(t, ts);
} else {
put = new Put(t);
}
try {
StringBuilder sb = new StringBuilder();
if (column != null && column.contains(":")) {
sb.append(column);
} else {
if (columnFamily != null) {
sb.append(columnFamily);
if (!columnFamily.endsWith(":")) {
sb.append(":");
}
if (column != null) {
sb.append(column);
}
}
}
byte[][] split =
KeyValue.parseColumn(Bytes.toBytes(sb.toString()));
if(split.length == 1) {
put.add(split[0], new byte[0], t);
} else {
put.add(split[0], split[1], t);
}
updater.put(put);
count++;
} catch (RuntimeException ex) {
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
ex.printStackTrace();
throw ex;
}
} catch (RuntimeException ex) {
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
ex.printStackTrace();
throw ex;
}
}
// Set start character back to FIRST_CHAR after we've done first loop.
thirdCharStart = FIRST_CHAR;
}
secondCharStart = FIRST_CHAR;
}
return count;
}
/**
* Implementors can flushcache.
*/
public static interface FlushCache {
/**
* @throws IOException
*/
public void flushcache() throws IOException;
}
/**
* Interface used by tests so can do common operations against an HTable
* or an HRegion.
*
* TOOD: Come up w/ a better name for this interface.
*/
public static interface Incommon {
/**
*
* @param delete
* @param writeToWAL
* @throws IOException
*/
public void delete(Delete delete, boolean writeToWAL)
throws IOException;
/**
* @param put
* @throws IOException
*/
public void put(Put put) throws IOException;
public Result get(Get get) throws IOException;
/**
* @param family
* @param qualifiers
* @param firstRow
* @param ts
* @return scanner for specified columns, first row and timestamp
* @throws IOException
*/
public ScannerIncommon getScanner(byte [] family, byte [][] qualifiers,
byte [] firstRow, long ts)
throws IOException;
}
/**
* A class that makes a {@link Incommon} out of a {@link HRegion}
*/
public static class HRegionIncommon implements Incommon, FlushCache {
final HRegion region;
/**
* @param HRegion
*/
public HRegionIncommon(final HRegion HRegion) {
this.region = HRegion;
}
public void put(Put put) throws IOException {
region.put(put);
}
public void delete(Delete delete, boolean writeToWAL)
throws IOException {
this.region.delete(delete, writeToWAL);
}
public Result get(Get get) throws IOException {
return region.get(get);
}
public ScannerIncommon getScanner(byte [] family, byte [][] qualifiers,
byte [] firstRow, long ts)
throws IOException {
Scan scan = new Scan(firstRow);
if(qualifiers == null || qualifiers.length == 0) {
scan.addFamily(family);
} else {
for(int i=0; i<qualifiers.length; i++){
scan.addColumn(HConstants.CATALOG_FAMILY, qualifiers[i]);
}
}
scan.setTimeRange(0, ts);
return new
InternalScannerIncommon(region.getScanner(scan));
}
public void flushcache() throws IOException {
this.region.flushcache();
}
}
/**
* A class that makes a {@link Incommon} out of a {@link HTable}
*/
public static class HTableIncommon implements Incommon {
final HTable table;
/**
* @param table
*/
public HTableIncommon(final HTable table) {
super();
this.table = table;
}
public void put(Put put) throws IOException {
table.put(put);
}
public void delete(Delete delete, boolean writeToWAL)
throws IOException {
this.table.delete(delete);
}
public Result get(Get get) throws IOException {
return table.get(get);
}
public ScannerIncommon getScanner(byte [] family, byte [][] qualifiers,
byte [] firstRow, long ts)
throws IOException {
Scan scan = new Scan(firstRow);
if(qualifiers == null || qualifiers.length == 0) {
scan.addFamily(family);
} else {
for(int i=0; i<qualifiers.length; i++){
scan.addColumn(HConstants.CATALOG_FAMILY, qualifiers[i]);
}
}
scan.setTimeRange(0, ts);
return new
ClientScannerIncommon(table.getScanner(scan));
}
}
public interface ScannerIncommon
extends Iterable<Result> {
public boolean next(List<KeyValue> values)
throws IOException;
public void close() throws IOException;
}
public static class ClientScannerIncommon implements ScannerIncommon {
ResultScanner scanner;
public ClientScannerIncommon(ResultScanner scanner) {
this.scanner = scanner;
}
public boolean next(List<KeyValue> values)
throws IOException {
Result results = scanner.next();
if (results == null) {
return false;
}
values.clear();
values.addAll(results.list());
return true;
}
public void close() throws IOException {
scanner.close();
}
@SuppressWarnings("unchecked")
public Iterator iterator() {
return scanner.iterator();
}
}
public static class InternalScannerIncommon implements ScannerIncommon {
InternalScanner scanner;
public InternalScannerIncommon(InternalScanner scanner) {
this.scanner = scanner;
}
public boolean next(List<KeyValue> results)
throws IOException {
return scanner.next(results);
}
public void close() throws IOException {
scanner.close();
}
public Iterator<Result> iterator() {
throw new UnsupportedOperationException();
}
}
// protected void assertCellEquals(final HRegion region, final byte [] row,
// final byte [] column, final long timestamp, final String value)
// throws IOException {
// Map<byte [], Cell> result = region.getFull(row, null, timestamp, 1, null);
// Cell cell_value = result.get(column);
// if (value == null) {
// assertEquals(Bytes.toString(column) + " at timestamp " + timestamp, null,
// cell_value);
// } else {
// if (cell_value == null) {
// fail(Bytes.toString(column) + " at timestamp " + timestamp +
// "\" was expected to be \"" + value + " but was null");
// }
// if (cell_value != null) {
// assertEquals(Bytes.toString(column) + " at timestamp "
// + timestamp, value, new String(cell_value.getValue()));
// }
// }
// }
protected void assertResultEquals(final HRegion region, final byte [] row,
final byte [] family, final byte [] qualifier, final long timestamp,
final byte [] value)
throws IOException {
Get get = new Get(row);
get.setTimeStamp(timestamp);
Result res = region.get(get);
NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> map =
res.getMap();
byte [] res_value = map.get(family).get(qualifier).get(timestamp);
if (value == null) {
assertEquals(Bytes.toString(family) + " " + Bytes.toString(qualifier) +
" at timestamp " + timestamp, null, res_value);
} else {
if (res_value == null) {
fail(Bytes.toString(family) + " " + Bytes.toString(qualifier) +
" at timestamp " + timestamp + "\" was expected to be \"" +
Bytes.toStringBinary(value) + " but was null");
}
if (res_value != null) {
assertEquals(Bytes.toString(family) + " " + Bytes.toString(qualifier) +
" at timestamp " +
timestamp, value, new String(res_value));
}
}
}
/**
* Common method to close down a MiniDFSCluster and the associated file system
*
* @param cluster
*/
public static void shutdownDfs(MiniDFSCluster cluster) {
if (cluster != null) {
LOG.info("Shutting down Mini DFS ");
try {
cluster.shutdown();
} catch (Exception e) {
/// Can get a java.lang.reflect.UndeclaredThrowableException thrown
// here because of an InterruptedException. Don't let exceptions in
// here be cause of test failure.
}
try {
FileSystem fs = cluster.getFileSystem();
if (fs != null) {
LOG.info("Shutting down FileSystem");
fs.close();
}
FileSystem.closeAll();
} catch (IOException e) {
LOG.error("error closing file system", e);
}
}
}
/**
* You must call {@link #closeRootAndMeta()} when done after calling this
* method. It does cleanup.
* @throws IOException
*/
protected void createRootAndMetaRegions() throws IOException {
root = HRegion.createHRegion(HRegionInfo.ROOT_REGIONINFO, testDir,
conf, HTableDescriptor.ROOT_TABLEDESC);
meta = HRegion.createHRegion(HRegionInfo.FIRST_META_REGIONINFO, testDir,
conf, HTableDescriptor.META_TABLEDESC);
HRegion.addRegionToMETA(root, meta);
}
protected void closeRootAndMeta() throws IOException {
HRegion.closeHRegion(meta);
HRegion.closeHRegion(root);
}
public static void assertByteEquals(byte[] expected,
byte[] actual) {
if (Bytes.compareTo(expected, actual) != 0) {
throw new AssertionFailedError("expected:<" +
Bytes.toString(expected) + "> but was:<" +
Bytes.toString(actual) + ">");
}
}
public static void assertEquals(byte[] expected,
byte[] actual) {
if (Bytes.compareTo(expected, actual) != 0) {
throw new AssertionFailedError("expected:<" +
Bytes.toStringBinary(expected) + "> but was:<" +
Bytes.toStringBinary(actual) + ">");
}
}
}