/* 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 org.voltdb.BackendTarget; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.client.NoConnectionsException; import org.voltdb.client.ProcCallException; import org.voltdb.client.ProcedureCallback; import org.voltdb.compiler.VoltProjectBuilder; import org.voltdb.compiler.VoltProjectBuilder.RoleInfo; import org.voltdb.compiler.VoltProjectBuilder.UserInfo; public class TestQueryTimeout extends RegressionSuite { private static final int TIMEOUT = 1000; // DEBUG build of EE runs much slower, so the timing part is not deterministic. private static String ERRORMSG = "A SQL query was terminated after"; private static final String INITIAL_STATUS = ""; private String m_errorStatusString; ProcedureCallback m_callback = new ProcedureCallback() { @Override public void clientCallback(ClientResponse clientResponse) throws Exception { m_errorStatusString = INITIAL_STATUS; if (clientResponse.getStatus() != ClientResponse.SUCCESS) { m_errorStatusString = clientResponse.getStatusString(); } } }; private void checkCallbackTimeoutError(String errorMsg) { assertTrue(m_errorStatusString + " did not contain " + ERRORMSG, m_errorStatusString.contains(errorMsg)); } private void checkCallbackSuccess() { assertEquals(INITIAL_STATUS, m_errorStatusString); } private void loadData(Client client, String tb, int scale) throws NoConnectionsException, IOException, ProcCallException { for (int i = 0; i < scale; i++) { client.callProcedure(m_callback, tb + ".insert", i, "MA", i % 6); } System.out.println("Finish loading " + scale + " rows for table " + tb); } private void truncateData(Client client, String tb) throws NoConnectionsException, IOException, ProcCallException { client.callProcedure("@AdHoc", "Truncate table " + tb); validateTableOfScalarLongs(client, "Select count(*) from " + tb, new long[]{0}); } private void loadTables(Client client, int scaleP, int scaleR) throws IOException, ProcCallException, InterruptedException { loadData(client, "P1", scaleP); loadData(client, "R1", scaleR); client.drain(); } private void truncateTables(Client client) throws IOException, ProcCallException, InterruptedException { truncateData(client, "P1"); truncateData(client, "R1"); } public void testReplicatedProcTimeout() throws IOException, ProcCallException, InterruptedException { if (isValgrind() || isDebug()) { // Disable the memcheck/debug for this test, it takes too long return; } System.out.println("test replicated table procedures timeout..."); m_username = "userWithAllProc"; m_password = "password"; Client client = this.getClient(); loadTables(client, 0, 5000); checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // // Replicated table procedure tests // try { client.callProcedure("ReplicatedReadOnlyProc"); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } // It's a write procedure and it's timed out safely because the MPI has not issue // any write query before it's timed out try { client.callProcedure("ReplicatedReadWriteProc"); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } // It's a write procedure and should not be timed out. try { client.callProcedure("ReplicatedWriteReadProc"); } catch (Exception ex) { fail("Write procedure should not be timed out"); } } public void testPartitionedProcTimeout() throws IOException, ProcCallException, InterruptedException { if (isValgrind() || isDebug()) { // Disable the memcheck/debug for this test, it takes too long return; } System.out.println("test partitioned table procedures timeout..."); m_username = "userWithAllProc"; m_password = "password"; Client client = this.getClient(); loadTables(client, 10000, 3000); checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // // Partition table procedure tests // try { client.callProcedure("PartitionReadOnlyProc"); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } // Read on partition table should not have MPI optimizations // so the MPI should mark it write and not time out the procedure try { client.callProcedure("PartitionReadWriteProc"); } catch (Exception ex) { fail("Write procedure should not be timed out"); } // It's a write procedure and should not be timed out. try { client.callProcedure("PartitionWriteReadProc"); } catch (Exception ex) { fail("Write procedure should not be timed out"); } } final String PROMPTMSG = " is supposed to timed out, but succeed eventually!"; private void checkTimeoutIncreasedProcSucceed(boolean sync, Client client, String procName, Object...params) throws IOException, ProcCallException, InterruptedException { checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); try { client.callProcedure(procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // increase the individual timeout value in order to succeed running this long procedure if (sync) { try { client.callProcedureWithTimeout(TIMEOUT*50, procName, params); } catch (Exception ex) { System.err.println(ex.getMessage()); fail(procName + " is supposed to succeed!"); } } else { client.callProcedureWithTimeout(m_callback, TIMEOUT*50, procName, params); client.drain(); checkCallbackSuccess(); } // check the global timeout value again checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // run the same procedure again to verify the global timeout value still applies try { client.callProcedure(procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); } private void checkTimeoutIncreasedProcFailed(boolean sync, Client client, String procName, Object...params) throws IOException, ProcCallException, InterruptedException { checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); try { client.callProcedure(procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // increase the individual timeout value in order to succeed running this long procedure // However, for non-admin user, timeout value can not override system timeout value. if (sync) { try { client.callProcedureWithTimeout(TIMEOUT*50, procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } } else { client.callProcedureWithTimeout(m_callback, TIMEOUT*50, procName, params); client.drain(); checkCallbackTimeoutError(ERRORMSG); } // check the global timeout value again checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // run the same procedure again to verify the global timeout value still applies try { client.callProcedure(procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); } private void checkTimeoutDecreaseProcFailed(boolean sync, Client client, String procName, Object...params) throws IOException, ProcCallException, InterruptedException { checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); try { client.callProcedure(procName, params); } catch (Exception ex) { fail(procName + " is supposed to succeed, but failed with message +\n" + ex.getMessage()); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // decrease the individual timeout value in order to timeout the running this long procedure if (sync) { try { client.callProcedureWithTimeout(TIMEOUT / 500, procName, params); fail(procName + PROMPTMSG); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + ERRORMSG, ex.getMessage().contains(ERRORMSG)); } } else { client.callProcedureWithTimeout(m_callback, TIMEOUT / 500, procName, params); client.drain(); checkCallbackTimeoutError(ERRORMSG);; } // check the global timeout value again checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); // run the same procedure again to verify the global timeout value still applies try { client.callProcedure(procName, params); } catch (Exception ex) { fail(procName + " is supposed to succeed!"); } checkDeploymentPropertyValue(client, "querytimeout", Integer.toString(TIMEOUT)); } private void checkIndividualProcTimeout(Client client, boolean isAdmin) throws IOException, ProcCallException, InterruptedException { // negative tests on the timeout value subtestNegativeIndividualProcTimeout(client); final String longRunningCrossJoinAggReplicated = "SELECT t1.contestant_number, t2.state, COUNT(*) " + "FROM R1 t1, R1 t2 " + "GROUP BY t1.contestant_number, t2.state;"; final String longRunningCrossJoinAggPartitioned = "SELECT t1.contestant_number, t2.state, COUNT(*) " + "FROM P1 t1, R1 t2 " + "GROUP BY t1.contestant_number, t2.state;"; boolean syncs[] = {true, false}; for (boolean sync : syncs) { System.out.println("Testing " + (sync ? "synchronously": "asynchronously") + " call"); // truncate the data truncateTables(client); // load more data loadTables(client, 10000, 3000); if (isAdmin) { checkTimeoutIncreasedProcSucceed(sync, client, "SPPartitionReadOnlyProc", 1); checkTimeoutIncreasedProcSucceed(sync, client, "PartitionReadOnlyProc"); checkTimeoutIncreasedProcSucceed(sync, client, "ReplicatedReadOnlyProc"); checkTimeoutIncreasedProcSucceed(sync, client, "@AdHoc", longRunningCrossJoinAggReplicated); checkTimeoutIncreasedProcSucceed(sync, client, "@AdHoc", longRunningCrossJoinAggPartitioned); checkTimeoutIncreasedProcSucceed(sync, client, "AdHocPartitionReadOnlyProc"); // first replicated read will be treated as READ ONLY checkTimeoutIncreasedProcSucceed(sync, client, "ReplicatedReadWriteProc"); } else { checkTimeoutIncreasedProcFailed(sync, client, "SPPartitionReadOnlyProc", 1); checkTimeoutIncreasedProcFailed(sync, client, "PartitionReadOnlyProc"); checkTimeoutIncreasedProcFailed(sync, client, "ReplicatedReadOnlyProc"); checkTimeoutIncreasedProcFailed(sync, client, "@AdHoc", longRunningCrossJoinAggReplicated); checkTimeoutIncreasedProcFailed(sync, client, "@AdHoc", longRunningCrossJoinAggPartitioned); checkTimeoutIncreasedProcFailed(sync, client, "AdHocPartitionReadOnlyProc"); // first replicated read will be treated as READ ONLY checkTimeoutIncreasedProcFailed(sync, client, "ReplicatedReadWriteProc"); } // write procedure no timing out checkNoTimingOutWriteProcedure(sync, client, "ReplicatedWriteReadProc"); checkNoTimingOutWriteProcedure(sync, client, "PartitionReadWriteProc"); checkNoTimingOutWriteProcedure(sync, client, "PartitionWriteReadProc"); // truncate the data truncateTables(client); loadTables(client, 1000, 300); checkTimeoutDecreaseProcFailed(sync, client, "SPPartitionReadOnlyProc", 1); checkTimeoutDecreaseProcFailed(sync, client, "PartitionReadOnlyProc"); checkTimeoutDecreaseProcFailed(sync, client, "ReplicatedReadOnlyProc"); checkTimeoutDecreaseProcFailed(sync, client, "@AdHoc", longRunningCrossJoinAggReplicated); checkTimeoutDecreaseProcFailed(sync, client, "@AdHoc", longRunningCrossJoinAggPartitioned); checkTimeoutDecreaseProcFailed(sync, client, "AdHocPartitionReadOnlyProc"); // first replicated read will be treated as READ ONLY checkTimeoutDecreaseProcFailed(sync, client, "ReplicatedReadWriteProc"); // write procedure no timing out checkNoTimingOutWriteProcedure(sync, client, "ReplicatedWriteReadProc"); checkNoTimingOutWriteProcedure(sync, client, "PartitionReadWriteProc"); checkNoTimingOutWriteProcedure(sync, client, "PartitionWriteReadProc"); // truncate the data truncateTables(client); } } public void testIndividualProcTimeout() throws IOException, ProcCallException, InterruptedException { if (isValgrind() || isDebug()) { // Disable the memcheck/debug for this test, it takes too long return; } Client client; m_username = "adminUser"; m_password = "password"; client = getClient(); checkIndividualProcTimeout(client, true); m_username = "userWithAllProc"; m_password = "password"; client = getClient(); checkIndividualProcTimeout(client, false); } private void subtestNegativeIndividualProcTimeout(Client client) throws IOException, ProcCallException, InterruptedException { // negative timeout value String errorMsg = "Timeout value can't be negative"; try { client.callProcedureWithTimeout(TIMEOUT * -1, "PartitionReadOnlyProc"); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + errorMsg, ex.getMessage().contains(errorMsg)); } try { client.callProcedureWithTimeout(m_callback, TIMEOUT * -1, "PartitionReadOnlyProc"); client.drain(); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + errorMsg, ex.getMessage().contains(errorMsg)); } // Integer overflow try { client.callProcedureWithTimeout(TIMEOUT * Integer.MAX_VALUE, "PartitionReadOnlyProc"); fail(); } catch (Exception ex) { assertTrue(ex.toString() + " did not contain " + errorMsg, ex.getMessage().contains(errorMsg)); } // underflow, asynchronously should be on the same path... } private void checkNoTimingOutWriteProcedure(boolean sync, Client client, String procName, Object...params) throws IOException, ProcCallException, InterruptedException { if (sync) { try { client.callProcedureWithTimeout(TIMEOUT / 500, procName, params); } catch (Exception ex) { System.err.println(ex.getMessage()); fail(procName + " is supposed to succeed!"); } } else { client.callProcedureWithTimeout(m_callback, TIMEOUT / 500, procName, params); client.drain(); checkCallbackSuccess(); } } // // Suite builder boilerplate // public TestQueryTimeout(String name) { super(name); } static final Class<?>[] PROCEDURES = { org.voltdb_testprocs.regressionsuites.querytimeout.ReplicatedReadOnlyProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.ReplicatedReadWriteProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.ReplicatedWriteReadProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.PartitionReadOnlyProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.PartitionReadWriteProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.PartitionWriteReadProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.SPPartitionReadOnlyProc.class, org.voltdb_testprocs.regressionsuites.querytimeout.AdHocPartitionReadOnlyProc.class }; static public junit.framework.Test suite() { VoltServerConfig config = null; MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder( TestQueryTimeout.class); VoltProjectBuilder project = new VoltProjectBuilder(); final String literalSchema = "CREATE TABLE R1 ( " + "phone_number INTEGER NOT NULL, " + "state VARCHAR(2) NOT NULL, " + "contestant_number INTEGER NOT NULL);" + "CREATE TABLE P1 ( " + "phone_number INTEGER NOT NULL, " + "state VARCHAR(2) NOT NULL, " + "contestant_number INTEGER NOT NULL);" + "PARTITION TABLE P1 ON COLUMN phone_number;" + "" ; try { project.addLiteralSchema(literalSchema); } catch (IOException e) { fail(); } project.addProcedures(PROCEDURES); project.setQueryTimeout(TIMEOUT); UserInfo users[] = new UserInfo[] { new UserInfo("adminUser", "password", new String[] {"AdMINISTRATOR"}), new UserInfo("userWithAllProc", "password", new String[] {"GroupWithAllProcPerm"}) }; project.addUsers(users); RoleInfo groups[] = new RoleInfo[] { new RoleInfo("GroupWithAllProcPerm", true, true, false, true, true, true) }; project.addRoles(groups); // suite defines its own ADMINISTRATOR user project.setSecurityEnabled(true, false); boolean success; config = new LocalCluster("querytimeout-onesite.jar", 1, 1, 0, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assertTrue(success); builder.addServerConfig(config); /* disabled until we work the kinks out of ipc support for fragment progress updates config = new LocalCluster("querytimeout-onesite.jar", 1, 1, 0, BackendTarget.NATIVE_EE_IPC); success = config.compile(project); assertTrue(success); builder.addServerConfig(config); */ // Cluster config = new LocalCluster("querytimeout-cluster.jar", 2, 3, 1, BackendTarget.NATIVE_EE_JNI); success = config.compile(project); assertTrue(success); builder.addServerConfig(config); return builder; } }