/***************************************************************************
* Copyright (C) 2011 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.VoltProcedure;
import org.voltdb.VoltSystemProcedure;
import org.voltdb.VoltType;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Cluster;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Host;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Site;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.catalog.Table;
import org.voltdb.client.Client;
import org.voltdb.client.ClientFactory;
import org.voltdb.utils.JarReader;
import org.voltdb.utils.VoltTypeUtil;
import edu.brown.benchmark.AbstractProjectBuilder;
import edu.brown.catalog.CatalogUtil;
import edu.brown.catalog.ClusterConfiguration;
import edu.brown.catalog.FixCatalog;
import edu.brown.hstore.HStore;
import edu.brown.hstore.HStoreSite;
import edu.brown.hstore.HStoreThreadManager;
import edu.brown.hstore.conf.HStoreConf;
import edu.brown.logging.LoggerUtil;
import edu.brown.mappings.ParameterMappingsSet;
import edu.brown.mappings.ParametersUtil;
import edu.brown.utils.ClassUtil;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.EventObservable;
import edu.brown.utils.EventObserver;
import edu.brown.utils.FileUtil;
import edu.brown.utils.PartitionEstimator;
import edu.brown.utils.ProjectType;
import edu.brown.utils.ThreadUtil;
/**
* Base class that provides a lot of the common functionality that our HStore test cases need
* @author pavlo
*/
public abstract class BaseTestCase extends TestCase implements UncaughtExceptionHandler {
private static final Logger LOG = Logger.getLogger(BaseTestCase.class);
protected static final boolean ENABLE_JAR_REUSE;
static {
// log4j Hack
LoggerUtil.setupLogging();
// Jar Caching!
boolean reuse = false;
if (System.getenv("ENABLE_JAR_REUSE") != null) {
reuse = Boolean.valueOf(System.getenv("ENABLE_JAR_REUSE"));
if (reuse) LOG.debug("ENABLE_JAR_REUSE = " + reuse);
}
ENABLE_JAR_REUSE = reuse;
// HStoreConf Hack
HStoreConf.init(null, null);
HStoreConf.singleton().site.cpu_affinity = false;
// Force everything to be single-threaded
ThreadUtil.setMaxGlobalThreads(2);
}
private ProjectType last_type;
/**
* There is always a static catalog that gets created for each project type
* This is so that for each test case invocation we don't have to recompile the catalog every time
*/
protected static CatalogContext catalogContext;
@Deprecated
protected static Catalog catalog;
@Deprecated
protected static Database catalog_db;
private static final Map<ProjectType, CatalogContext> project_catalogs = new HashMap<ProjectType, CatalogContext>();
private static final Set<ProjectType> needs_reset = new HashSet<ProjectType>();
protected static PartitionEstimator p_estimator;
private static final Map<ProjectType, PartitionEstimator> project_p_estimators = new HashMap<ProjectType, PartitionEstimator>();
private final static AtomicBoolean is_first = new AtomicBoolean(true);
/**
* Setup the test case for the given project type
* By default we don't include foreign keys in the catalog (I forget why we did this)
* @param type
* @throws Exception
*/
protected void setUp(ProjectType type) throws Exception {
this.setUp(type, false, true);
}
/**
* Setup the test case for the given project type
* @param type
* @param fkeys - if true, then
* @throws Exception
*/
protected void setUp(ProjectType type, boolean fkeys) throws Exception {
this.setUp(type, fkeys, true);
}
protected void setUp(AbstractProjectBuilder projectBuilder) throws Exception {
this.setUp(projectBuilder, false);
}
protected void setUp(AbstractProjectBuilder projectBuilder, boolean force) throws Exception {
super.setUp();
this.last_type = ProjectType.TEST;
if (force == false) {
catalogContext = project_catalogs.get(this.last_type);
}
if (catalogContext == null || force) {
String catalogJar = new File(projectBuilder.getJarName(true)).getAbsolutePath();
try {
boolean status = projectBuilder.compile(catalogJar);
assert(status) : "Failed to compile " + catalogJar;
} catch (Exception ex) {
throw new RuntimeException("Failed to create " + projectBuilder.getProjectName() + " catalog [" + catalogJar + "]", ex);
}
Catalog c = new Catalog();
try {
// read in the catalog
String serializedCatalog = JarReader.readFileFromJarfile(catalogJar, CatalogUtil.CATALOG_FILENAME);
// create the catalog (that will be passed to the ClientInterface
c.execute(serializedCatalog);
} catch (Exception ex) {
throw new RuntimeException("Failed to load " + projectBuilder.getProjectName() + " catalog [" + catalogJar + "]", ex);
}
CatalogContext cc = new CatalogContext(c, catalogJar);
this.init(this.last_type, cc);
}
catalog = catalogContext.catalog;
catalog_db = catalogContext.database;
p_estimator = project_p_estimators.get(this.last_type);
}
/**
* Main setUp method for test cases. Given the ProjectType we will populate the static catalog field members
* The full_catalog flag is a hack to work around OutofMemory issues with TPC-E
* @param type
* @param fkeys
* @param full_catalog
* @throws Exception
*/
protected void setUp(ProjectType type, boolean fkeys, boolean full_catalog) throws Exception {
super.setUp();
this.last_type = type;
catalogContext = project_catalogs.get(type);
if (catalogContext == null) {
CatalogContext cc = null;
AbstractProjectBuilder projectBuilder = getProjectBuilder(type);
if (ENABLE_JAR_REUSE) {
File jar_path = projectBuilder.getJarPath(true);
if (needs_reset.contains(type)) {
jar_path.delete();
needs_reset.remove(type);
}
if (jar_path.exists()) {
LOG.debug("LOAD CACHE JAR: " + jar_path.getAbsolutePath());
cc = CatalogUtil.loadCatalogContextFromJar(jar_path);
} else {
LOG.debug("MISSING JAR: " + jar_path.getAbsolutePath());
}
}
if (cc == null) {
File jarPath = projectBuilder.getJarPath(true);
Catalog c = null;
switch (type) {
case TPCE:
c = projectBuilder.createCatalog(fkeys, full_catalog);
break;
default:
c = projectBuilder.getFullCatalog(fkeys);
if (LOG.isDebugEnabled())
LOG.debug(type + " Catalog JAR: " + jarPath.getAbsolutePath());
break;
} // SWITCH
assert(c != null);
cc = new CatalogContext(c, jarPath);
}
assert(cc != null) : "Unexpected null catalog for " + type;
//if (type == ProjectType.TPCC) ParametersUtil.populateCatalog(CatalogUtil.getDatabase(catalog), ParametersUtil.getParameterMapping(type));
this.init(type, cc);
}
catalog = catalogContext.catalog;
catalog_db = catalogContext.database;
p_estimator = project_p_estimators.get(type);
}
/**
* Reset all cached objects for this project type, including
* removing any jars that many already exist.
* @param type
*/
protected void reset(ProjectType type) {
p_estimator = null;
project_catalogs.remove(type);
project_p_estimators.remove(type);
needs_reset.add(type);
}
/**
* Store the catalog for this ProjectType and generate the supporting classes
* @param type
* @param catalog
*/
private void init(ProjectType type, CatalogContext cc) {
assertNotNull(cc);
project_catalogs.put(type, cc);
catalogContext = cc;
catalog = catalogContext.catalog;
catalog_db = catalogContext.database;
p_estimator = new PartitionEstimator(catalogContext);
assertNotNull(p_estimator);
project_p_estimators.put(type, p_estimator);
}
public static AbstractProjectBuilder getProjectBuilder(ProjectType type) {
AbstractProjectBuilder projectBuilder = AbstractProjectBuilder.getProjectBuilder(type);
// 2012-10-11: We have to disable any replicated secondary indexes
// because it will screw up a bunch of tests that we already have setup
projectBuilder.enableReplicatedSecondaryIndexes(false);
projectBuilder.removeReplicatedSecondaryIndexes();
assert(projectBuilder != null);
return (projectBuilder);
}
public static File getCatalogJarPath(ProjectType type) {
return (getProjectBuilder(type).getJarPath(true));
}
public static File getDDLPath(ProjectType type) {
return (new File(getProjectBuilder(type).getDDLURL(true).getFile()));
}
/**
* Returns true if this is the first time setup() has been called.
* This method will always return true the first time it is invoked.
* All subsequent calls will return false.
* Useful for updating the catalog.
*/
protected final static boolean isFirstSetup() {
return (is_first.compareAndSet(true, false));
}
/**
* Returns true if we have access to the Volt lib in our local system
* @return
*/
protected final boolean hasVoltLib() throws Exception {
File obj_dir = FileUtil.findDirectory("obj");
// Figure out whether we are on a machine that has the native lib
// we can use right now
if (obj_dir != null) {
File so_path = new File(obj_dir.getAbsolutePath() + "/release/nativelibs/libvoltdb.so");
if (so_path.exists()) {
System.load(so_path.getAbsolutePath());
return (true);
}
}
return (false);
}
protected final void applyParameterMappings(ProjectType type) throws Exception {
// We need the correlations file in order to make sure the parameters
// get mapped properly
File correlations_path = this.getParameterMappingsFile(type);
if (correlations_path != null) {
ParameterMappingsSet correlations = new ParameterMappingsSet();
correlations.load(correlations_path, catalog_db);
ParametersUtil.applyParameterMappings(catalog_db, correlations);
}
}
// --------------------------------------------------------------------------------------
// CONVENIENCE METHODS
// --------------------------------------------------------------------------------------
protected final CatalogContext getCatalogContext() {
assertNotNull(catalogContext);
return (catalogContext);
}
protected final Catalog getCatalog() {
assertNotNull(catalogContext);
return (catalogContext.catalog);
}
protected final Database getDatabase() {
assertNotNull(catalogContext);
return (catalogContext.database);
}
protected final Cluster getCluster() {
assertNotNull(catalogContext);
return (catalogContext.cluster);
}
protected final Site getSite(int site_id) {
assertNotNull(catalogContext);
Site catalog_site = catalogContext.getSiteById(site_id);
assert(catalog_site != null) : "Failed to retrieve Site #" + site_id + " from catalog";
return (catalog_site);
}
protected final Table getTable(Database catalog_db, String table_name) {
assertNotNull(catalog_db);
Table catalog_tbl = catalog_db.getTables().getIgnoreCase(table_name);
assert(catalog_tbl != null) : "Failed to retrieve '" + table_name + "' table from catalog";
return (catalog_tbl);
}
protected final Table getTable(String table_name) {
return getTable(catalog_db, table_name);
}
protected final Column getColumn(Database catalog_db, Table catalog_tbl, String col_name) {
assertNotNull(catalog_db);
assertNotNull(catalog_tbl);
Column catalog_col = catalog_tbl.getColumns().getIgnoreCase(col_name);
assert(catalog_col != null) : "Failed to retrieve Column '" + col_name + "' from Table '" + catalog_tbl.getName() + "'";
return (catalog_col);
}
protected final Column getColumn(Table catalog_tbl, String col_name) {
return (getColumn(catalog_db, catalog_tbl, col_name));
}
protected final Column getColumn(Database catalog_db, String table_name, String col_name) {
return (getColumn(catalog_db, this.getTable(catalog_db, table_name), col_name));
}
protected final Column getColumn(String table_name, String col_name) {
return (getColumn(catalog_db, this.getTable(table_name), col_name));
}
protected final Column getColumn(Table catalog_tbl, int col_idx) {
int num_columns = catalog_tbl.getColumns().size();
if (col_idx < 0) col_idx = num_columns + col_idx; // Python!
assert(col_idx >= 0 && col_idx < num_columns) : "Invalid column index for " + catalog_tbl + ": " + col_idx;
Column catalog_col = catalog_tbl.getColumns().get(col_idx);
assert(catalog_col != null) : "Failed to retrieve Column at '" + col_idx + "' from Table '" + catalog_tbl.getName() + "'";
return (catalog_col);
}
protected final Procedure getProcedure(Database catalog_db, String proc_name) {
assertNotNull(catalog_db);
Procedure catalog_proc = catalog_db.getProcedures().getIgnoreCase(proc_name);
assert(catalog_proc != null) : "Failed to retrieve '" + proc_name + "' Procedure from catalog";
return (catalog_proc);
}
protected final Procedure getProcedure(String proc_name) {
return getProcedure(catalog_db, proc_name);
}
@SuppressWarnings("unchecked")
protected final Procedure getProcedure(Database catalog_db, Class<? extends VoltProcedure> proc_class) {
String procName;
if (ClassUtil.getSuperClasses(proc_class).contains(VoltSystemProcedure.class)) {
procName = VoltSystemProcedure.procCallName((Class<? extends VoltSystemProcedure>)proc_class);
} else {
procName = proc_class.getSimpleName();
}
return getProcedure(catalog_db, procName);
}
protected final Procedure getProcedure(Class<? extends VoltProcedure> proc_class) {
return getProcedure(catalog_db, proc_class);
}
protected final ProcParameter getProcParameter(Database catalog_db, Procedure catalog_proc, int idx) {
assertNotNull(catalog_db);
assert(idx >= 0) : "Invalid ProcParameter index for " + catalog_proc + ": " + idx;
assert(idx < catalog_proc.getParameters().size()) : "Invalid ProcParameter index for " + catalog_proc + ": " + idx;
ProcParameter catalog_param = catalog_proc.getParameters().get(idx);
assertNotNull("Null ProcParameter index for " + catalog_proc + ": " + idx, catalog_param);
return (catalog_param);
}
protected final ProcParameter getProcParameter(Procedure catalog_proc, int idx) {
return getProcParameter(catalog_db, catalog_proc, idx);
}
protected final ProcParameter getProcParameter(Class<? extends VoltProcedure> proc_class, int idx) {
return getProcParameter(catalog_db, this.getProcedure(proc_class), idx);
}
protected final Statement getStatement(Database catalog_db, Procedure catalog_proc, String stmt_name) {
assertNotNull(catalog_db);
assertNotNull(catalog_proc);
Statement catalog_stmt = catalog_proc.getStatements().getIgnoreCase(stmt_name);
assert(catalog_stmt != null) : "Failed to retrieve Statement '" + stmt_name + "' from Procedure '" + catalog_proc.getName() + "'";
return (catalog_stmt);
}
protected final Statement getStatement(Procedure catalog_proc, String stmt_name) {
return getStatement(catalog_db, catalog_proc, stmt_name);
}
protected final Statement getStatement(Class<? extends VoltProcedure> proc_class, String stmt_name) {
Procedure catalog_proc = getProcedure(proc_class);
return getStatement(catalog_db, catalog_proc, stmt_name);
}
/**
* Add fake partitions to the loaded catalog
* Assuming that there is one partition per site
* @param num_partitions
*/
protected final void addPartitions(int num_partitions) throws Exception {
// HACK! If we already have this many partitions in the catalog, then we won't recreate it
// This fixes problems where we need to reference the same catalog objects in multiple test cases
if (catalogContext.numberOfPartitions != num_partitions) {
ClusterConfiguration cc = new ClusterConfiguration();
for (Integer i = 0; i < num_partitions; i++) {
cc.addPartition("localhost", 0, i);
// System.err.println("[" + i + "] " + Arrays.toString(triplets.lastElement()));
} // FOR
Catalog c = FixCatalog.cloneCatalog(catalog, cc);
this.init(this.last_type, new CatalogContext(c, catalogContext.jarPath));
}
Cluster cluster = CatalogUtil.getCluster(catalog_db);
assertEquals(num_partitions, cluster.getNum_partitions());
assertEquals(num_partitions, catalogContext.numberOfPartitions);
}
/**
* Update the catalog to include new hosts/sites/partitions
* @param num_hosts
* @param num_sites
* @param num_partitions
* @throws Exception
*/
protected final void initializeCatalog(int num_hosts, int num_sites, int num_partitions) throws Exception {
// HACK! If we already have this many partitions in the catalog, then we won't recreate it
// This fixes problems where we need to reference the same catalog objects in multiple test cases
if (catalogContext.numberOfHosts != num_hosts ||
catalogContext.numberOfSites != (num_hosts * num_sites) ||
catalogContext.numberOfPartitions != (num_hosts * num_sites * num_partitions)) {
// HACK
String hostFormat = (num_hosts == 1 ? "localhost" : "host%02d");
Catalog c = FixCatalog.cloneCatalog(catalog, hostFormat, num_hosts, num_sites, num_partitions);
CatalogContext cc = new CatalogContext(c, catalogContext.jarPath);
this.init(this.last_type, cc);
}
Cluster cluster = catalogContext.cluster;
assertEquals(num_hosts, catalogContext.numberOfHosts);
assertEquals((num_hosts * num_sites), catalogContext.numberOfSites);
assertEquals((num_hosts * num_sites * num_partitions), catalogContext.numberOfPartitions);
assertEquals((num_hosts * num_sites * num_partitions), cluster.getNum_partitions());
}
/**
* Convenience method for launching an HStoreSite for the given Site
* This will block until the HStoreSite has sucecssfully started
* @param catalog_site
* @param hstore_conf
* @return
* @throws InterruptedException
*/
protected final HStoreSite createHStoreSite(Site catalog_site, HStoreConf hstore_conf) throws Exception {
assert(hasVoltLib()) : "Missing EE shared library";
final CountDownLatch readyLock = new CountDownLatch(1);
final EventObserver<HStoreSite> ready = new EventObserver<HStoreSite>() {
@Override
public void update(EventObservable<HStoreSite> o, HStoreSite arg) {
readyLock.countDown();
}
};
HStoreSite hstore_site = HStore.initialize(catalogContext, catalog_site.getId(), hstore_conf);
hstore_site.getReadyObservable().addObserver(ready);
Thread thread = new Thread(hstore_site);
thread.start();
// Wait at least 15 seconds or until we know that our HStoreSite has started
boolean isReady = readyLock.await(15, TimeUnit.SECONDS);
assertTrue("Failed to start HStoreSite for " + catalog_site, isReady);
// I added an extra little sleep just to be sure...
ThreadUtil.sleep(3000);
assertNotNull(hstore_site);
return (hstore_site);
}
/**
* Convenience method for creating a client connection to a random HStoreSite
* This assumes that the H-Store cluster is up and running properly
* @return
* @throws Exception
*/
protected final Client createClient() throws Exception {
// Connect to random site and using a random port that it's listening on
Site catalog_site = CollectionUtil.random(catalogContext.sites);
assertNotNull(catalog_site);
Host catalog_host = catalog_site.getHost();
assertNotNull(catalog_host);
String hostName = catalog_host.getIpaddr();
int port = catalog_site.getProc_port();
LOG.debug(String.format("Creating new client connection to HStoreSite %s at %s:%d",
HStoreThreadManager.formatSiteName(catalog_site.getId()),
hostName, port));
Client client = ClientFactory.createClient(128, null, false, null);
client.createConnection(null, hostName, port, "user", "password");
return (client);
}
// --------------------------------------------------------------------------------------
// FILE LOADING METHODS
// --------------------------------------------------------------------------------------
/**
* Find a trace file for a given project type
* @param current
* @param type
* @return
* @throws IOException
*/
public File getWorkloadFile(ProjectType type) throws IOException {
String suffix = "";
switch (type) {
case TPCC:
suffix = ".100p-1";
break;
default:
suffix = "-1";
break;
} // SWITCH
return (this.getWorkloadFile(type, suffix));
}
public File getWorkloadFile(ProjectType type, String suffix) throws IOException {
return (type.getProjectFile(new File(".").getCanonicalFile(), "workloads", suffix+".trace"));
}
/**
* Find a stats cache file for a given project type
* @param current
* @param type
* @return
* @throws IOException
*/
public File getStatsFile(ProjectType type) throws IOException {
return (type.getProjectFile(new File(".").getCanonicalFile(), "stats", ".stats"));
}
/**
* Find a parameter correlations file for a given project type
* @param current
* @param type
* @return
* @throws IOException
*/
public File getParameterMappingsFile(ProjectType type) throws IOException {
// HACK HACK HACK
File srcDir = FileUtil.findDirectory("src");
File mappingsFile = FileUtil.join(srcDir.getAbsolutePath(),
"benchmarks",
type.getPackageName().replace(".", File.separator),
type.name().toLowerCase() + ".mappings");
return (mappingsFile);
// return (this.getProjectFile(new File(".").getCanonicalFile(), type, "mappings", ".mappings"));
}
// /**
// * Find a Markov file for a given project type
// * @param current
// * @param type
// * @return
// * @throws IOException
// */
// public File getMarkovFile(ProjectType type) throws IOException {
// return (this.getProjectFile(new File(".").getCanonicalFile(), type, "markovs", ".markovs"));
// }
/**
* Generate an array of random input parameters for a given Statement
* @param catalog_stmt
* @return
*/
protected Object[] randomStatementParameters(Statement catalog_stmt) {
Object params[] = new Object[catalog_stmt.getParameters().size()];
for (StmtParameter catalog_param : catalog_stmt.getParameters()) {
VoltType vtype = VoltType.get(catalog_param.getJavatype());
params[catalog_param.getIndex()] = VoltTypeUtil.getRandomValue(vtype);
LOG.debug(catalog_param.fullName() + " -> " + params[catalog_param.getIndex()] + " / " + vtype);
} // FOR
return (params);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
e.printStackTrace();
fail(e.getMessage()); // XXX: I don't think this gets picked up
}
}