/* 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.
*/
/*
* This test is used for testing round trip DDL through Adhoc and SQLcmd.
* We first build a catalog and pull the canonical DDL from it.
* Then we feed this DDL to a bare server through Adhoc/SQLcmd,
* pull the canonical DDL again, and check whether it remains the same.
*/
package org.voltdb.canonicalddl;
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.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import org.junit.Test;
import org.voltcore.utils.PortGenerator;
import org.voltcore.utils.ssl.SSLConfiguration;
import org.voltdb.AdhocDDLTestBase;
import org.voltdb.VoltDB;
import org.voltdb.VoltDB.Configuration;
import org.voltdb.client.ClientConfig;
import org.voltdb.compiler.VoltCompiler;
import org.voltdb.compiler.VoltProjectBuilder;
import org.voltdb.fullddlfeatures.TestDDLFeatures;
import org.voltdb.utils.MiscUtils;
public class TestCanonicalDDLThroughSQLcmd extends AdhocDDLTestBase {
private String firstCanonicalDDL = null;
private boolean triedSqlcmdDryRun = false;
private String getFirstCanonicalDDL() throws Exception {
String pathToCatalog = Configuration.getPathToCatalogForTest("fullDDL.jar");
VoltCompiler compiler = new VoltCompiler(false);
final URL url = TestDDLFeatures.class.getResource("fullDDL.sql");
String pathToSchema = URLDecoder.decode(url.getPath(), "UTF-8");
boolean success = compiler.compileFromDDL(pathToCatalog, pathToSchema);
assertTrue(success);
return compiler.getCanonicalDDL();
}
private void secondCanonicalDDLFromAdhoc() throws Exception {
String pathToCatalog = Configuration.getPathToCatalogForTest("emptyDDL.jar");
String pathToDeployment = Configuration.getPathToCatalogForTest("emptyDDL.xml");
VoltCompiler compiler = new VoltCompiler(false);
VoltProjectBuilder builder = new VoltProjectBuilder();
builder.setUseDDLSchema(true);
boolean success = builder.compile(pathToCatalog);
assertTrue(success);
MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment);
VoltDB.Configuration config = new VoltDB.Configuration();
config.m_pathToCatalog = pathToCatalog;
config.m_pathToDeployment = pathToDeployment;
startSystem(config);
m_client.callProcedure("@AdHoc", firstCanonicalDDL);
// First line of canonical DDL differs thanks to creation time. Avoid
// it in the comparison
// Sanity check that we're not trimming the entire fullddl.sql file away
assertTrue(firstCanonicalDDL.indexOf('\n') < 100);
String secondDDL = compiler.getCanonicalDDL();
assertEquals(firstCanonicalDDL.substring(firstCanonicalDDL.indexOf('\n')),
secondDDL.substring(secondDDL.indexOf('\n')));
teardownSystem();
}
private void secondCanonicalDDLFromSQLcmd(boolean fastModeDDL) throws Exception {
String pathToCatalog = Configuration.getPathToCatalogForTest("emptyDDL.jar");
String pathToDeployment = Configuration.getPathToCatalogForTest("emptyDDL.xml");
VoltProjectBuilder builder = new VoltProjectBuilder();
builder.setUseDDLSchema(true);
PortGenerator pg = new PortGenerator();
int httpdPort = pg.next();
builder.setHTTPDPort(httpdPort);
boolean success = builder.compile(pathToCatalog);
assertTrue(success);
MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment);
VoltDB.Configuration config = new VoltDB.Configuration();
config.m_pathToCatalog = pathToCatalog;
config.m_pathToDeployment = pathToDeployment;
startSystem(config);
String roundtripDDL;
assert(firstCanonicalDDL != null);
if ( ! triedSqlcmdDryRun) {
assertEquals("sqlcmd dry run failed -- maybe some sqlcmd component (the voltdb jar file?) needs to be rebuilt.",
0, callSQLcmd("\n", fastModeDDL));
triedSqlcmdDryRun = true;
}
assertEquals("sqlcmd failed on input:\n" + firstCanonicalDDL, 0, callSQLcmd(firstCanonicalDDL, fastModeDDL));
roundtripDDL = getDDLFromHTTP(httpdPort);
// IZZY: we force single statement SQL keywords to lower case, it seems
// Sanity check that we're not trimming the entire fullddl.sql file away
assertTrue(firstCanonicalDDL.indexOf('\n') < 100);
assertEquals(firstCanonicalDDL.substring(firstCanonicalDDL.indexOf('\n')).toLowerCase(),
roundtripDDL.substring(roundtripDDL.indexOf('\n')).toLowerCase());
assertEquals("sqlcmd failed on last call", 0, callSQLcmd("CREATE TABLE NONSENSE (id INTEGER);\n", fastModeDDL));
roundtripDDL = getDDLFromHTTP(httpdPort);
assertTrue(firstCanonicalDDL.indexOf('\n') < 100);
assertFalse(firstCanonicalDDL.substring(firstCanonicalDDL.indexOf('\n')).toLowerCase().equals(
roundtripDDL.substring(roundtripDDL.indexOf('\n')).toLowerCase()));
teardownSystem();
}
private int callSQLcmd(String ddl, boolean fastModeDDL) throws Exception {
String commandPath = "bin/sqlcmd";
final long timeout = 300000; // 300,000 millis -- give up after 5 minutes of trying.
File f = new File("ddl.sql");
f.deleteOnExit();
FileOutputStream fos = new FileOutputStream(f);
fos.write(ddl.getBytes());
fos.close();
File out = new File("out.log");
File error = new File("error.log");
ProcessBuilder pb = null;
if (fastModeDDL) {
pb = new ProcessBuilder(commandPath, "--ddl-file=" + f.getPath());
} else {
pb = new ProcessBuilder(commandPath);
pb.redirectInput(f);
}
pb.redirectOutput(out);
pb.redirectError(error);
Process process = pb.start();
// Set timeout to 1 minute
long starttime = System.currentTimeMillis();
long elapsedtime = 0;
long pollcount = 0;
do {
Thread.sleep(1000);
try {
int exitValue = process.exitValue();
// Only verbosely report the successful exit after verbosely reporting a delay.
// Frequent false alarms might lead to raising the sleep time.
if (pollcount > 0) {
elapsedtime = System.currentTimeMillis() - starttime;
System.err.println("External process (" + commandPath + ") exited after being polled " +
pollcount + " times over " + elapsedtime + "ms");
}
// Debug the SQLCMD output if needed
// System.out.println(new Scanner(out).useDelimiter("\\Z").next());
return exitValue;
}
catch (IllegalThreadStateException notYetDone) {
elapsedtime = System.currentTimeMillis() - starttime;
++pollcount;
System.err.println("External process (" + commandPath + ") has not yet exited after " + elapsedtime + "ms");
continue;
}
catch (Exception e) {
elapsedtime = System.currentTimeMillis() - starttime;
++pollcount;
System.err.println("External process (" + commandPath + ") has not yet exited after " + elapsedtime + "ms");
}
} while (elapsedtime < timeout);
fail("External process (" + commandPath + ") timed out after " + elapsedtime + "ms on input:\n" + ddl);
return -1;
}
private String getDDLFromHTTP(int httpdPort) throws Exception {
URL ddlURL;
HttpURLConnection conn;
if (ClientConfig.ENABLE_SSL_FOR_TEST) {
SSLContext sslCtx = SSLConfiguration.createSslContext(new SSLConfiguration.SslConfig());
ddlURL = new URL(String.format("https://localhost:%d/ddl/", httpdPort));
HttpsURLConnection connection = (HttpsURLConnection)ddlURL.openConnection();
connection.setSSLSocketFactory(sslCtx.getSocketFactory());
connection.setHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
@Override
public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("localhost")) {
return true;
}
return false;
}
});
connection.setRequestMethod("POST");
connection.setDoOutput(true);
try {
connection.connect();
}
catch (IOException ioe) {
System.err.println("failed to connect to " + ddlURL);
ioe.printStackTrace();
}
conn = connection;
} else {
ddlURL = new URL(String.format("http://localhost:%d/ddl/", httpdPort));
conn = (HttpURLConnection) ddlURL.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
try {
conn.connect();
}
catch (IOException ioe) {
System.err.println("failed to connect to " + ddlURL);
ioe.printStackTrace();
}
}
BufferedReader in = null;
try {
if (conn.getInputStream() != null) {
in = new BufferedReader(
new InputStreamReader(
conn.getInputStream(), "UTF-8"));
}
} catch (IOException e) {
e.printStackTrace();
}
if (in == null) {
throw new Exception("Unable to read response from server");
}
String line;
StringBuffer sb = new StringBuffer();
while ((line = in.readLine()) != null) {
sb.append(line + "\n");
}
return sb.toString();
}
@Test
public void testCanonicalDDLRoundtrip() throws Exception {
firstCanonicalDDL = getFirstCanonicalDDL();
long starttime = System.currentTimeMillis();
secondCanonicalDDLFromAdhoc();
long adHocTime = System.currentTimeMillis() - starttime;
starttime = System.currentTimeMillis();
secondCanonicalDDLFromSQLcmd(false);
long sqlcmdTime = System.currentTimeMillis() - starttime;
starttime = System.currentTimeMillis();
secondCanonicalDDLFromSQLcmd(true);
long sqlcmdTimeDDLmode = System.currentTimeMillis() - starttime;
System.out.println(String.format("AdHoc elapsed %d ms", adHocTime));
System.out.println(String.format("SQLcmd elapsed %d ms", sqlcmdTime));
System.out.println(String.format("SQLcmd fast DDL mode elapsed %d ms", sqlcmdTimeDDLmode));
}
}