/* 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 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.File; import java.io.IOException; import org.junit.Test; import org.voltdb.AdhocDDLTestBase; import org.voltdb.ClientResponseImpl; import org.voltdb.VoltDB; import org.voltdb.VoltDB.Configuration; import org.voltdb.VoltTable; import org.voltdb.VoltTableTestHelpers; import org.voltdb.client.Client; import org.voltdb.client.ClientConfig; import org.voltdb.client.ClientFactory; import org.voltdb.client.ClientResponse; import org.voltdb.client.ProcCallException; import org.voltdb.compiler.VoltCompiler; import org.voltdb.compiler.VoltProjectBuilder; import org.voltdb.compiler.VoltProjectBuilder.RoleInfo; import org.voltdb.compiler.VoltProjectBuilder.UserInfo; import org.voltdb.utils.Encoder; import org.voltdb.utils.InMemoryJarfile; import org.voltdb.utils.MiscUtils; import org.voltdb_testprocs.updateclasses.jars.TestProcedure; /** * Tests a mix of multi-partition and single partition procedures on a * mix of replicated and partititioned tables on a mix of single-site and * multi-site VoltDB instances. * */ public class TestUpdateClasses extends AdhocDDLTestBase { static Class<?>[] PROC_CLASSES = { org.voltdb_testprocs.updateclasses.testImportProc.class, org.voltdb_testprocs.updateclasses.testCreateProcFromClassProc.class }; static Class<?>[] EXTRA_CLASSES = { org.voltdb_testprocs.updateclasses.NoMeaningClass.class }; static Class<?>[] COLLIDING_CLASSES = { org.voltdb_testprocs.fullddlfeatures.testImportProc.class, org.voltdb_testprocs.fullddlfeatures.testCreateProcFromClassProc.class }; @Test public void testBasic() throws Exception { System.out.println("\n\n-----\n testBasic \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); // This is maybe cheating a little bit? InMemoryJarfile jarfile = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : EXTRA_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } // Add a deployment file just to have something other than classes in the jar jarfile.put("deployment.xml", new File(pathToDeployment)); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); System.out.println(resp.getResults()[0]); // New cluster, you're like summer vacation... assertEquals(0, resp.getResults()[0].getRowCount()); assertFalse(VoltTableTestHelpers.moveToMatchingRow(resp.getResults()[0], "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); boolean threw = false; try { resp = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("was not found")); threw = true; } assertTrue(threw); // First, some tests of incorrect parameters // only 1 param threw = false; try { resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes()); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("UpdateClasses system procedure requires exactly two parameters")); threw = true; } assertTrue(threw); // wrong jarfile param type threw = false; try { resp = m_client.callProcedure("@UpdateClasses", 10L, null); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("UpdateClasses system procedure takes the jarfile bytes as a byte array")); threw = true; } assertTrue(threw); // wrong delete string param type threw = false; try { resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), 10L); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("UpdateClasses system procedure takes the list of classes")); threw = true; } assertTrue(threw); resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); // Are we still like summer vacation? resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); VoltTable results = resp.getResults()[0]; System.out.println(results); assertEquals(3, results.getRowCount()); assertTrue(VoltTableTestHelpers.moveToMatchingRow(results, "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); assertEquals(1L, results.getLong("VOLT_PROCEDURE")); assertEquals(0L, results.getLong("ACTIVE_PROC")); // Can we turn it into a procedure? resp = m_client.callProcedure("@AdHoc", "create procedure from class " + PROC_CLASSES[0].getCanonicalName() + ";"); System.out.println(((ClientResponseImpl)resp).toJSONString()); resp = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); results = resp.getResults()[0]; assertEquals(10L, results.asScalarLong()); } finally { teardownSystem(); } } @Test public void testRoleControl() throws Exception { System.out.println("\n\n-----\n testRoleControl \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); RoleInfo groups[] = new RoleInfo[] { new RoleInfo("adhoc", true, false, false, false, false, false) }; UserInfo users[] = new UserInfo[] { new UserInfo("adhocuser", "adhocuser", new String[] {"adhoc"}), new UserInfo("sysuser", "sysuser", new String[] {"ADMINISTRATOR"}) }; builder.addRoles(groups); builder.addUsers(users); // Test defines its own ADMIN user builder.setSecurityEnabled(true, false); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); // This is maybe cheating a little bit? InMemoryJarfile jarfile = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : EXTRA_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } Client auth_client = null; try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; // Default client auth is going to fail, catch and keep chugging try { startSystem(config); } catch (IOException ioe) { assertTrue(ioe.getMessage().contains("Authentication rejected")); } m_client.close(); // reconnect m_client with auth that will connect but no sysproc powers ClientConfig bad_config = new ClientConfig("adhocuser", "adhocuser"); m_client = ClientFactory.createClient(bad_config); m_client.createConnection("localhost"); // Need a client with the right auth ClientConfig auth_config = new ClientConfig("sysuser", "sysuser"); auth_client = ClientFactory.createClient(auth_config); auth_client.createConnection("localhost"); ClientResponse resp; resp = auth_client.callProcedure("@SystemCatalog", "CLASSES"); System.out.println(resp.getResults()[0]); // New cluster, you're like summer vacation... assertEquals(0, resp.getResults()[0].getRowCount()); assertFalse(VoltTableTestHelpers.moveToMatchingRow(resp.getResults()[0], "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); boolean threw = false; try { resp = auth_client.callProcedure(PROC_CLASSES[0].getSimpleName()); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("was not found")); threw = true; } assertTrue(threw); threw = false; try { resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("does not have admin permission")); threw = true; } assertTrue(threw); resp = auth_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); // Are we still like summer vacation? resp = auth_client.callProcedure("@SystemCatalog", "CLASSES"); VoltTable results = resp.getResults()[0]; System.out.println(results); assertEquals(3, results.getRowCount()); assertTrue(VoltTableTestHelpers.moveToMatchingRow(results, "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); assertEquals(1L, results.getLong("VOLT_PROCEDURE")); assertEquals(0L, results.getLong("ACTIVE_PROC")); } finally { if (auth_client != null) { auth_client.close(); } teardownSystem(); } } @Test public void testCollidingClasses() throws Exception { System.out.println("\n\n-----\n testCollidingProc \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); // This is maybe cheating a little bit? InMemoryJarfile jarfile = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : EXTRA_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); System.out.println(resp.getResults()[0]); // New cluster, you're like summer vacation... assertEquals(0, resp.getResults()[0].getRowCount()); assertFalse(VoltTableTestHelpers.moveToMatchingRow(resp.getResults()[0], "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); boolean threw = false; try { resp = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()); } catch (ProcCallException pce) { assertTrue(pce.getMessage().contains("was not found")); threw = true; } assertTrue(threw); resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); // Are we still like summer vacation? resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); VoltTable results = resp.getResults()[0]; System.out.println(results); assertEquals(3, results.getRowCount()); assertTrue(VoltTableTestHelpers.moveToMatchingRow(results, "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); assertEquals(1L, results.getLong("VOLT_PROCEDURE")); assertEquals(0L, results.getLong("ACTIVE_PROC")); // Can we turn it into a procedure? resp = m_client.callProcedure("@AdHoc", "create procedure from class " + PROC_CLASSES[0].getCanonicalName() + ";"); System.out.println(((ClientResponseImpl)resp).toJSONString()); resp = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); results = resp.getResults()[0]; assertEquals(10L, results.asScalarLong()); // now, let's collide identically simpleName'd classes InMemoryJarfile boom = new InMemoryJarfile(); for (Class<?> clazz : COLLIDING_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(boom, clazz); } resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); // should be okay to have classnames with same simplename resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); results = resp.getResults()[0]; System.out.println(results); assertEquals(5, results.getRowCount()); assertTrue(VoltTableTestHelpers.moveToMatchingRow(results, "CLASS_NAME", COLLIDING_CLASSES[0].getCanonicalName())); assertEquals(1L, results.getLong("VOLT_PROCEDURE")); assertEquals(0L, results.getLong("ACTIVE_PROC")); } finally { teardownSystem(); } } @Test public void testNonJarInput() throws Exception { System.out.println("\n\n-----\n testNonJarInput \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; try { resp = m_client.callProcedure("@UpdateClasses", Encoder.hexEncode("This is not a pipe"), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); } catch (ProcCallException pce) { pce.printStackTrace(); } // Check that we haven't made any obvious changes to the state based on garbage resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); System.out.println(resp.getResults()[0]); assertEquals(0, resp.getResults()[0].getRowCount()); } finally { teardownSystem(); } } @Test public void testInnerClasses() throws Exception { System.out.println("\n\n-----\n testInnerClasses \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); // Something sane ought to work ClientResponse resp; InMemoryJarfile boom = new InMemoryJarfile(); VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.InnerClassesTestProc.class); try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); } catch (ProcCallException pce) { pce.printStackTrace(); fail("Loading proc with inner classes should succeed"); } // Error in non-visible inner class static initializer? boolean threw = false; boom = new InMemoryJarfile(); comp = new VoltCompiler(false); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.BadInnerClassesTestProc.class); try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); } catch (ProcCallException pce) { pce.printStackTrace(); threw = true; } assertTrue("Bad inner class should have failed", threw); } finally { teardownSystem(); } } @Test public void testBadInitializerClasses() throws Exception { System.out.println("\n\n-----\n testBadInitializerClasses \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; InMemoryJarfile boom = new InMemoryJarfile(); VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.testBadInitializerProc.class); boolean threw = false; try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); } catch (ProcCallException pce) { pce.printStackTrace(); threw = true; } assertTrue("Bad class jar should have thrown", threw); threw = false; boom = new InMemoryJarfile(); comp = new VoltCompiler(false); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.BadClassLoadClass.class); try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); System.out.println(((ClientResponseImpl)resp).toJSONString()); } catch (ProcCallException pce) { pce.printStackTrace(); threw = true; } assertTrue("Bad class jar should have thrown", threw); } finally { teardownSystem(); } } // Delete tests: // single file match // * match // ** match // comma-separated matches // combine new jarfile with deleted stuff // deleting inner classes @Test public void testDeleteClasses() throws Exception { System.out.println("\n\n-----\n testCollidingProc \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("-- Don't care"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); // This is maybe cheating a little bit? InMemoryJarfile jarfile = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : EXTRA_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : COLLIDING_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; // Make sure we're clean resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); assertEquals(0, resp.getResults()[0].getRowCount()); // Add the jarfile we built resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); assertEquals(PROC_CLASSES.length + EXTRA_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); // remove one class assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); resp = m_client.callProcedure("@UpdateClasses", null, PROC_CLASSES[0].getCanonicalName()); assertFalse(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); // remove everything under fullddlfeatures assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); resp = m_client.callProcedure("@UpdateClasses", null, "org.voltdb_testprocs.fullddlfeatures.*"); assertFalse(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertFalse(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); // Remove everything left assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(EXTRA_CLASSES[0].getCanonicalName())); resp = m_client.callProcedure("@UpdateClasses", null, "org.voltdb**"); assertFalse(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertFalse(findClassInSystemCatalog(EXTRA_CLASSES[0].getCanonicalName())); resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); assertEquals(0, resp.getResults()[0].getRowCount()); // put everything back // Add the jarfile we built resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); assertEquals(PROC_CLASSES.length + EXTRA_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); // delete the common simple names from both packages simultaneously resp = m_client.callProcedure("@UpdateClasses", null, "**testImportProc , **testCreateProcFromClassProc"); resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // should be the only thing left assertEquals(1, resp.getResults()[0].getRowCount()); assertTrue(findClassInSystemCatalog(EXTRA_CLASSES[0].getCanonicalName())); // make a jar without the extra InMemoryJarfile jarfile2 = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile2, clazz); } for (Class<?> clazz : COLLIDING_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile2, clazz); } // finally, delete what's left and put the new jar in simultaneously resp = m_client.callProcedure("@UpdateClasses", jarfile2.getFullJarBytes(), "**updateclasses.*"); resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // extra class should be gone, others installed assertEquals(PROC_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); assertFalse(findClassInSystemCatalog(EXTRA_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); // now add a class with inner classes InMemoryJarfile inner = new InMemoryJarfile(); VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(inner, org.voltdb_testprocs.updateclasses.InnerClassesTestProc.class); resp = m_client.callProcedure("@UpdateClasses", inner.getFullJarBytes(), null); // old stuff should have survived assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); // Did we get the new class and inner classes too? assertTrue(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc")); assertTrue(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerNotPublic")); assertTrue(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithConstructorArgs")); assertTrue(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithEasyConstructor")); assertTrue(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithNoConstructor")); // now just delete the parent class resp = m_client.callProcedure("@UpdateClasses", null, "org.voltdb_testprocs.updateclasses.InnerClassesTestProc"); // old stuff should have survived resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // Non-inner stuff should have survived assertEquals(PROC_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); // Inner classes and parent gone assertFalse(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc")); assertFalse(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerNotPublic")); assertFalse(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithConstructorArgs")); assertFalse(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithEasyConstructor")); assertFalse(findClassInSystemCatalog("org.voltdb_testprocs.updateclasses.InnerClassesTestProc$InnerWithNoConstructor")); // Empty string has no effect resp = m_client.callProcedure("@UpdateClasses", null, ""); // old stuff should have survived resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // Non-inner stuff should have survived assertEquals(PROC_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); // pattern that matches nothing has no effect resp = m_client.callProcedure("@UpdateClasses", null, "com.voltdb.*"); // old stuff should have survived resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // Non-inner stuff should have survived assertEquals(PROC_CLASSES.length + COLLIDING_CLASSES.length, resp.getResults()[0].getRowCount()); assertTrue(findClassInSystemCatalog(PROC_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(PROC_CLASSES[1].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[0].getCanonicalName())); assertTrue(findClassInSystemCatalog(COLLIDING_CLASSES[1].getCanonicalName())); } finally { teardownSystem(); } } @Test public void testStatsAfterUpdateClasses() throws Exception { System.out.println("\n\n-----\n testStatsAfterUpdateClasses \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema("create table tb1 (a int);"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 1, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); // This is maybe cheating a little bit? InMemoryJarfile jarfile = new InMemoryJarfile(); for (Class<?> clazz : PROC_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } for (Class<?> clazz : EXTRA_CLASSES) { VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(jarfile, clazz); } // Add a deployment file just to have something other than classes in the jar jarfile.put("deployment.xml", new File(pathToDeployment)); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; VoltTable vt; resp = m_client.callProcedure("@SystemCatalog", "CLASSES"); // New cluster, you're like summer vacation... assertEquals(0, resp.getResults()[0].getRowCount()); assertFalse(VoltTableTestHelpers.moveToMatchingRow(resp.getResults()[0], "CLASS_NAME", PROC_CLASSES[0].getCanonicalName())); resp = m_client.callProcedure("@UpdateClasses", jarfile.getFullJarBytes(), null); // check stats after UAC vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(1, vt.getRowCount()); vt.advanceRow(); assertEquals("org.voltdb.sysprocs.UpdateApplicationCatalog", vt.getString(5)); // create procedure 0 resp = m_client.callProcedure("@AdHoc", "create procedure from class " + PROC_CLASSES[0].getCanonicalName() + ";"); // check stats after UAC vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(vt.getRowCount(), 1); vt.advanceRow(); assertEquals("org.voltdb.sysprocs.UpdateApplicationCatalog", vt.getString(5)); // invoke a new user procedure vt = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()).getResults()[0]; assertEquals(10L, vt.asScalarLong()); vt = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()).getResults()[0]; assertEquals(10L, vt.asScalarLong()); vt = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()).getResults()[0]; assertEquals(10L, vt.asScalarLong()); // check stats vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(2, vt.getRowCount()); assertTrue(vt.toString().contains("org.voltdb_testprocs.updateclasses.testImportProc")); assertTrue(vt.toString().contains("org.voltdb.sysprocs.UpdateApplicationCatalog")); // create procedure 1 resp = m_client.callProcedure("@AdHoc", "create procedure from class " + PROC_CLASSES[1].getCanonicalName() + ";"); // check stats vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(1, vt.getRowCount()); vt.advanceRow(); assertEquals("org.voltdb.sysprocs.UpdateApplicationCatalog", vt.getString(5)); resp = m_client.callProcedure(PROC_CLASSES[1].getSimpleName(), 1l, "", ""); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(2, vt.getRowCount()); vt = m_client.callProcedure(PROC_CLASSES[0].getSimpleName()).getResults()[0]; assertEquals(10L, vt.asScalarLong()); vt = m_client.callProcedure("@Statistics", "PROCEDURE", 0).getResults()[0]; assertEquals(3, vt.getRowCount()); } finally { teardownSystem(); } } @Test public void testCreateProceduresBeforeUpdateClasses() throws Exception { System.out.println("\n\n-----\n testCreateProceduresBeforeUpdateClasses \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema( "create table t1 (a int, b int); \n" + "create procedure proc1 as select a from t1 where b = ?;"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; InMemoryJarfile boom = new InMemoryJarfile(); VoltCompiler comp = new VoltCompiler(false); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.NoMeaningClass.class); comp.addClassToJar(boom, org.voltdb_testprocs.updateclasses.testImportProc.class); try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); } catch (ProcCallException pce) { fail("@UpdateClasses should not fail with message: " + pce.getMessage()); } } finally { teardownSystem(); } } // See ENG-12536: Test UpdateClasses with changed SQLStmts @Test public void testUpdateClassesWithSQLStmtChanges() throws Exception { System.out.println("\n\n-----\n testUpdateClassesWithSQLStmtChanges \n-----\n\n"); String pathToCatalog = Configuration.getPathToCatalogForTest("updateclasses.jar"); String pathToDeployment = Configuration.getPathToCatalogForTest("updateclasses.xml"); VoltProjectBuilder builder = new VoltProjectBuilder(); builder.addLiteralSchema( "create table tt (PID varchar(20 BYTES) NOT NULL, CITY varchar(6 BYTES), " + "CONSTRAINT IDX_TT_PKEY PRIMARY KEY (PID)); \n" + "PARTITION TABLE TT ON COLUMN PID;\n"); builder.setUseDDLSchema(true); boolean success = builder.compile(pathToCatalog, 2, 1, 0); assertTrue("Schema compilation failed", success); MiscUtils.copyFile(builder.getPathToDeployment(), pathToDeployment); try { VoltDB.Configuration config = new VoltDB.Configuration(); config.m_pathToCatalog = pathToCatalog; config.m_pathToDeployment = pathToDeployment; startSystem(config); ClientResponse resp; // Testing system can load jar file from class path, but not the internal class files try { resp = m_client.callProcedure("TestProcedure", "12345", "boston"); fail("TestProcedure is not loaded"); } catch (ProcCallException e) { assertTrue(e.getMessage().contains("Procedure TestProcedure was not found")); } try { Class.forName("voter.TestProcedure"); fail("Should not load the class file from the jar file on disk automatically"); } catch (ClassNotFoundException e) { assertTrue(e.getMessage().contains("voter.TestProcedure")); } InMemoryJarfile boom = new InMemoryJarfile(TestProcedure.class.getResource("addSQLStmt.jar")); resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); resp = m_client.callProcedure("@AdHoc", "create procedure partition ON TABLE tt COLUMN pid from class voter.TestProcedure;"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); resp = m_client.callProcedure("TestProcedure", "12345", "boston"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); // UpdateClass with the new changed StmtSQL jar boom = new InMemoryJarfile(TestProcedure.class.getResource("addSQLStmtNew.jar")); resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); // run with a new query without problems resp = m_client.callProcedure("TestProcedure", "12345", "boston"); assertEquals(ClientResponse.SUCCESS, resp.getStatus()); // Invalid SQLStmt should fail during UpdateClasses boom = new InMemoryJarfile(TestProcedure.class.getResource("addSQLStmtInvalid.jar")); try { resp = m_client.callProcedure("@UpdateClasses", boom.getFullJarBytes(), null); fail("Invalid SQLStmt should fail during UpdateClasses"); } catch (ProcCallException e) { assertTrue(e.getMessage().contains("Failed to plan for statement")); assertTrue(e.getMessage().contains("user lacks privilege or object not found: TT_INVALID_QUERY")); } } finally { teardownSystem(); } } }