/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* 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 org.voltdb.regressionsuites;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.voltdb.BackendTarget;
import org.voltdb.VoltTable;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.iv2.MpInitiator;
import org.voltdb.utils.MiscUtils;
import org.voltdb_testprocs.regressionsuites.malicious.GoSleep;
import junit.framework.Test;
public class StatisticsTestSuiteBase extends SaveRestoreBase {
protected final static int SITES = 2;
protected final static int HOSTS = 3;
protected final static int KFACTOR = MiscUtils.isPro() ? 1 : 0;
protected final static int PARTITIONS = (SITES * HOSTS) / (KFACTOR + 1);
protected final static String jarName = "statistics-cluster.jar";
protected final static boolean hasLocalServer = false;
protected static StringBuilder m_recentAnalysis = null;
protected final static int FSYNC_INTERVAL_GOLD = 50;
protected final static String drSchema =
"CREATE TABLE EMPLOYEE (\n"
+ "E_ID INTEGER NOT NULL,\n"
+ "E_AGE INTEGER NOT NULL"
+ ");\n"
+ "DR TABLE EMPLOYEE;\n";
protected static final Class<?>[] PROCEDURES
= {
GoSleep.class
};
public StatisticsTestSuiteBase(String name) {
super(name);
}
private String claimRecentAnalysis() {
String result = "No root cause analysis is available for this failure.";
if (m_recentAnalysis != null) {
result = m_recentAnalysis.toString();
m_recentAnalysis = null;
}
return result;
}
// validation functions supporting multiple columns
private boolean checkRowForMultipleTargets(VoltTable result, Map<String, String> columnTargets) {
for (Entry<String, String> entry : columnTargets.entrySet()) {
if (!result.getString(entry.getKey()).equals(entry.getValue())) {
return false;
}
}
return true;
}
private int countHostsProvidingRows(VoltTable result, Map<String, String> columnTargets,
boolean enforceUnique) {
result.resetRowPosition();
Set<Long> hostsSeen = new HashSet<>();
while (result.advanceRow()) {
if (checkRowForMultipleTargets(result, columnTargets)) {
Long thisHostId = result.getLong("HOST_ID");
if (enforceUnique) {
StringBuilder message = new StringBuilder();
message.append("HOST_ID: " + thisHostId + " seen twice in table looking for ");
for (Entry<String, String> entry : columnTargets.entrySet()) {
message.append(entry.getValue() + " in column " + entry.getKey() + ";");
}
assertFalse(message.toString(), hostsSeen.contains(thisHostId));
}
hostsSeen.add(thisHostId);
}
}
//* Enable this to force a failure with diagnostics */ hostsSeen.add(123456789L);
// Before possibly failing an assert, prepare to report details of the non-conforming result.
m_recentAnalysis = null;
if (HOSTS != hostsSeen.size()) {
m_recentAnalysis = new StringBuilder();
m_recentAnalysis.append("Failure follows from these results:\n");
Set<Long> seenAgain = new HashSet<>();
result.resetRowPosition();
while (result.advanceRow()) {
Long thisHostId = result.getLong("HOST_ID");
String rowStatus = "Found a non-match";
if (checkRowForMultipleTargets(result, columnTargets)) {
if (seenAgain.add(thisHostId)) {
rowStatus = "Added a match";
} else {
rowStatus = "Duplicated a match";
}
}
m_recentAnalysis.append(rowStatus
+ " at host " + thisHostId + " for ");
for (String key : columnTargets.keySet()) {
m_recentAnalysis.append(key + " " + result.getString(key) + ";");
}
m_recentAnalysis.append("\n");
}
}
return hostsSeen.size();
}
// For the provided table, verify that there is a row for each host in the cluster where
// the column designated by each key of columnTargets has the value corresponding to this
// key in columnTargets. For example, for Initiator stats, if there is an entry
// <'PROCEDURE_NAME', 'foo> in columnTargets, this will verify that the initiator at each
// node has seen a procedure invocation for 'foo'
protected void validateRowSeenAtAllHosts(VoltTable result, Map<String, String> columnTargets,
boolean enforceUnique) {
result.resetRowPosition();
int hostCount = countHostsProvidingRows(result, columnTargets, enforceUnique);
assertEquals(claimRecentAnalysis(), HOSTS, hostCount);
}
protected boolean validateRowSeenAtAllSites(VoltTable result, String columnName, String targetValue,
boolean enforceUnique) {
result.resetRowPosition();
Set<Long> sitesSeen = new HashSet<>();
while (result.advanceRow()) {
String colValFromRow = result.getString(columnName);
if (targetValue.equalsIgnoreCase(colValFromRow)) {
long hostId = result.getLong("HOST_ID");
long thisSiteId = result.getLong("SITE_ID");
thisSiteId |= hostId << 32;
if (enforceUnique) {
assertFalse("SITE_ID: " + thisSiteId + " seen twice in table looking for " + targetValue
+ " in column " + columnName, sitesSeen.contains(thisSiteId));
}
sitesSeen.add(thisSiteId);
}
}
return (HOSTS * SITES) == sitesSeen.size();
}
// For the provided table, verify that there is a row for each partition in the cluster where
// the column designated by 'columnName' has the value 'targetValue'.
protected void validateRowSeenAtAllPartitions(VoltTable result, String columnName, String targetValue,
boolean enforceUnique) {
result.resetRowPosition();
Set<Integer> partsSeen = new HashSet<>();
while (result.advanceRow()) {
String colValFromRow = result.getString(columnName);
if (targetValue.equalsIgnoreCase(colValFromRow)) {
int thisPartId = (int) result.getLong("PARTITION_ID");
if (enforceUnique) {
assertFalse("PARTITION_ID: " + thisPartId + " seen twice in table looking for " + targetValue
+ " in column " + columnName, partsSeen.contains(thisPartId));
}
partsSeen.add(thisPartId);
}
}
// Remove the MPI in case it's in there
partsSeen.remove(MpInitiator.MP_INIT_PID);
assertEquals(PARTITIONS, partsSeen.size());
}
static public Test suite(Class classzz, boolean isCommandLogTest) throws IOException {
return suite(classzz, isCommandLogTest, -1);
}
//
// Build a list of the tests to be run. Use the regression suite
// helpers to allow multiple backends.
// JUnit magic that uses the regression suite helper classes.
//
static public Test suite(Class classzz, boolean isCommandLogTest, int replicationPort) throws IOException {
return suite(classzz, isCommandLogTest, replicationPort, true);
}
static public Test suite(Class classzz, boolean isCommandLogTest, int replicationPort, boolean reuseServer) throws IOException {
VoltServerConfig config = null;
MultiConfigSuiteBuilder builder
= new MultiConfigSuiteBuilder(classzz);
// Not really using TPCC functionality but need a database.
// The testLoadMultipartitionTable procedure assumes partitioning
// on warehouse id.
VoltProjectBuilder project = new VoltProjectBuilder();
project.addLiteralSchema(
"CREATE TABLE WAREHOUSE (\n"
+ " W_ID SMALLINT DEFAULT '0' NOT NULL,\n"
+ " W_NAME VARCHAR(16) DEFAULT NULL,\n"
+ " W_STREET_1 VARCHAR(32) DEFAULT NULL,\n"
+ " W_STREET_2 VARCHAR(32) DEFAULT NULL,\n"
+ " W_CITY VARCHAR(32) DEFAULT NULL,\n"
+ " W_STATE VARCHAR(2) DEFAULT NULL,\n"
+ " W_ZIP VARCHAR(9) DEFAULT NULL,\n"
+ " W_TAX FLOAT DEFAULT NULL,\n"
+ " W_YTD FLOAT DEFAULT NULL,\n"
+ " CONSTRAINT W_PK_TREE PRIMARY KEY (W_ID)\n"
+ ");\n"
+ "CREATE TABLE ITEM (\n"
+ " I_ID INTEGER DEFAULT '0' NOT NULL,\n"
+ " I_IM_ID INTEGER DEFAULT NULL,\n"
+ " I_NAME VARCHAR(32) DEFAULT NULL,\n"
+ " I_PRICE FLOAT DEFAULT NULL,\n"
+ " I_DATA VARCHAR(64) DEFAULT NULL,\n"
+ " CONSTRAINT I_PK_TREE PRIMARY KEY (I_ID)\n"
+ ");\n"
+ "CREATE TABLE NEW_ORDER (\n"
+ " NO_W_ID SMALLINT DEFAULT '0' NOT NULL\n"
+ ");\n");
project.addPartitionInfo("WAREHOUSE", "W_ID");
project.addPartitionInfo("NEW_ORDER", "NO_W_ID");
project.addProcedures(PROCEDURES);
// Enable asynchronous logging for test of commandlog test
if (MiscUtils.isPro() && isCommandLogTest) {
project.configureLogging(null, null, false, true, FSYNC_INTERVAL_GOLD, null, null);
}
/*
* Create a cluster configuration.
* Some of the sysproc results come back a little strange when applied to a cluster that is being
* simulated through LocalCluster -- all the hosts have the same HOSTNAME, just different host ids.
* So, these tests shouldn't rely on the usual uniqueness of host names in a cluster.
*/
config = new LocalCluster(jarName, StatisticsTestSuiteBase.SITES,
StatisticsTestSuiteBase.HOSTS, StatisticsTestSuiteBase.KFACTOR,
BackendTarget.NATIVE_EE_JNI);
((LocalCluster) config).setHasLocalServer(hasLocalServer);
if (MiscUtils.isPro() && isCommandLogTest) {
((LocalCluster) config).setJavaProperty("LOG_SEGMENT_SIZE", "1");
((LocalCluster) config).setJavaProperty("LOG_SEGMENTS", "1");
}
if (replicationPort > 0) {
// cluster id is default to 0
project.addLiteralSchema(drSchema);
project.setDrProducerEnabled();
((LocalCluster) config).setReplicationPort(replicationPort);
((LocalCluster) config).overrideAnyRequestForValgrind();
}
boolean success = config.compile(project);
assertTrue(success);
builder.addServerConfig(config, reuseServer);
return builder;
}
}