/* 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.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.PrintWriter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Properties; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.json_voltpatches.JSONArray; import org.json_voltpatches.JSONException; import org.json_voltpatches.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.voltcore.utils.CoreUtils; import org.voltdb.BackendTarget; import org.voltdb.VoltDB.SimulatedExitException; import org.voltdb.client.Client; import org.voltdb.client.ClientFactory; import org.voltdb.common.Constants; import org.voltdb.compiler.VoltProjectBuilder; import org.voltdb.regressionsuites.JUnit4LocalClusterTest; import org.voltdb.regressionsuites.LocalCluster; import org.voltdb_testprocs.regressionsuites.failureprocs.CrashJVM; import org.voltdb_testprocs.regressionsuites.failureprocs.CrashVoltDBProc; import com.google_voltpatches.common.base.Charsets; public class TestCollector extends JUnit4LocalClusterTest { private static final int STARTUP_DELAY = 3000; VoltProjectBuilder builder; LocalCluster cluster; String listener; Client client; String m_voltDbRootPath; // used for specifying output file name to cli. Also used to store // the output file path, after collector call, so that file can be deleted String m_outputFileName = ""; String m_prefix = ""; boolean resetCurrentTime = true; String m_collectBaseFolder; int m_pid; @Before public void setUp() throws Exception { String simpleSchema = "create table blah (" + "ival bigint default 0 not null, " + "PRIMARY KEY(ival));"; builder = new VoltProjectBuilder(); builder.addLiteralSchema(simpleSchema); builder.addProcedures(CrashJVM.class); builder.addProcedures(CrashVoltDBProc.class); cluster = new LocalCluster("collect.jar", 2, 1, 0, BackendTarget.NATIVE_EE_JNI); cluster.setHasLocalServer(false); boolean success = cluster.compile(builder); assert (success); File voltDbRoot; cluster.startUp(true); //Get server specific root after startup. if (cluster.isNewCli()) { voltDbRoot = new File(cluster.getServerSpecificRoot("0")); } else { String voltDbFilePrefix = cluster.getSubRoots().get(0).getPath(); voltDbRoot = new File(voltDbFilePrefix, builder.getPathToVoltRoot().getPath()); } m_voltDbRootPath = voltDbRoot.getPath(); listener = cluster.getListenerAddresses().get(0); client = ClientFactory.createClient(); client.createConnection(listener); m_outputFileName = ""; m_pid = getpid(m_voltDbRootPath); m_prefix = ""; System.setProperty("VOLT_JUSTATEST", "true"); } @After public void tearDown() throws Exception { client.close(); cluster.shutDown(); deleteOutputFileIfExists(); } private ZipFile collect(boolean skipHeapDump, int days, boolean force) throws Exception { if(resetCurrentTime) { Collector.m_currentTimeMillis = System.currentTimeMillis(); } String pathToOutputFile = ""; ArrayList<String> cliParams = new ArrayList<>(15); cliParams.add("--voltdbroot=" + m_voltDbRootPath); if (m_prefix.isEmpty()) { cliParams.add("--prefix=\"\""); } else { cliParams.add("--prefix=" + m_prefix); pathToOutputFile = System.getProperty("user.dir") + File.separator + m_prefix + "_" + Collector.PREFIX_DEFAULT_COLLECT_FILE + "_" + CoreUtils.getHostnameOrAddress() + Collector.COLLECT_FILE_EXTENSION; } cliParams.add("--dryrun=false"); // dryRun cliParams.add("--skipheapdump=" + String.valueOf(skipHeapDump)); cliParams.add("--days=" + String.valueOf(days)); cliParams.add("--libPathForTest=" + getWorkingDir(m_voltDbRootPath) + "/lib"); cliParams.add("--force=" + String.valueOf(force)); if (!m_outputFileName.isEmpty()) { cliParams.add("--outputFile=" + m_outputFileName); pathToOutputFile = m_outputFileName; } if (pathToOutputFile.trim().isEmpty()) { pathToOutputFile = Collector.PREFIX_DEFAULT_COLLECT_FILE + "_" + CoreUtils.getHostnameOrAddress() + Collector.COLLECT_FILE_EXTENSION; } Collector.main(cliParams.toArray(new String[cliParams.size()])); m_collectBaseFolder = Collector.getZipCollectFolderBase(); File collectionFile = new File(pathToOutputFile); assertTrue(collectionFile.exists()); m_outputFileName = pathToOutputFile; return new ZipFile(collectionFile); } private int getpid(String voltDbRootPath) throws Exception { File configLogDir = new File(voltDbRootPath, Constants.CONFIG_DIR); File configInfo = new File(configLogDir, "config.json"); JSONObject jsonObject = Collector.parseJSONFile(configInfo.getCanonicalPath()); int pid = jsonObject.getInt("pid"); return pid; } private String getWorkingDir(String voltDbRootPath) throws Exception { File configLogDir = new File(voltDbRootPath, Constants.CONFIG_DIR); File configInfo = new File(configLogDir, "config.json"); JSONObject jsonObject = Collector.parseJSONFile(configInfo.getCanonicalPath()); String workingDir = jsonObject.getString("workingDir"); return workingDir; } private List<String> getLogPaths(String voltDbRootPath) throws Exception { File configLogDir = new File(voltDbRootPath, Constants.CONFIG_DIR); File configInfo = new File(configLogDir, "config.json"); JSONObject jsonObject = Collector.parseJSONFile(configInfo.getCanonicalPath()); List<String> logPaths = new ArrayList<String>(); JSONArray jsonArray = jsonObject.getJSONArray("log4jDst"); for (int i = 0; i < jsonArray.length(); i++) { String path = jsonArray.getJSONObject(i).getString("path"); logPaths.add(path); } return logPaths; } private void createLogFiles() throws Exception { try { String configInfoPath = m_voltDbRootPath + File.separator + Constants.CONFIG_DIR + File.separator + "config.json";; JSONObject jsonObject= Collector.parseJSONFile(configInfoPath); JSONArray jsonArray = jsonObject.getJSONArray("log4jDst"); //maintain the file naming format String fileNamePrefix = "volt-junit-fulllog.txt."; String fileText = "This is a dummy log file."; String workingDir = getWorkingDir(m_voltDbRootPath); VoltFile logFolder = new VoltFile(workingDir + "/obj/release/testoutput/"); logFolder.mkdir(); for(File oldLogFile : logFolder.listFiles()) { if(oldLogFile.getName().startsWith(fileNamePrefix)) { oldLogFile.delete(); } } SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); String[] fileDates = new String[6]; Calendar cal, cal2; cal = Calendar.getInstance(); cal2 = Calendar.getInstance(); for(int i=-1; i < 2; i++) { cal.add(Calendar.DATE, -i-1); fileDates[i+1] = formatter.format(cal.getTime()); } cal = Calendar.getInstance(); cal.add(Calendar.YEAR, -1); cal2.set(cal.get(Calendar.YEAR), 11, 31); fileDates[3] = formatter.format(cal2.getTime()); cal2.add(Calendar.DATE, -4); fileDates[4] = formatter.format(cal2.getTime()); cal2 = Calendar.getInstance(); cal2.set(cal2.get(Calendar.YEAR), 0, 02); fileDates[5] = formatter.format(cal2.getTime()); for(String fileDate: fileDates) { VoltFile file = new VoltFile(logFolder, fileNamePrefix + fileDate); file.createNewFile(); BufferedWriter writer = new BufferedWriter(new FileWriter(file.getAbsolutePath())); writer.write(fileText); writer.close(); formatter.format(file.lastModified()); file.setLastModified(formatter.parse(fileDate).getTime()); JSONObject object = new JSONObject(); object.put("path", file.getCanonicalPath()); object.put("format", "'.'" + fileDate); jsonArray.put(object); } VoltFile repeatFileFolder = new VoltFile(logFolder, "test"); repeatFileFolder.mkdir(); VoltFile file = new VoltFile(repeatFileFolder, fileNamePrefix + fileDates[0]); file.createNewFile(); BufferedWriter writer = new BufferedWriter(new FileWriter(file.getAbsolutePath())); writer.write(fileText); writer.close(); JSONObject object = new JSONObject(); object.put("path", file.getCanonicalPath()); object.put("format", "'.'" + fileDates[0]); jsonArray.put(object); FileOutputStream fos = new FileOutputStream(configInfoPath); fos.write(jsonObject.toString(4).getBytes(Charsets.UTF_8)); fos.close(); } catch (JSONException e) { System.err.print(e.getMessage()); } catch (ParseException e) { System.err.print(e.getMessage()); } } private void deleteOutputFileIfExists() { File outputFile = new File(m_outputFileName); if (outputFile.exists()) { assertTrue(outputFile.delete()); } } private void verifyBasicTestCollect(ZipFile collectionZip) throws Exception { String subFolderPath = m_collectBaseFolder + File.separator; ZipEntry heapdumpFile = collectionZip.getEntry(subFolderPath + "heap_dumps" + File.separator + "java_pid" + m_pid + ".hprof"); assertNotNull(heapdumpFile); ZipEntry catalogJar = collectionZip.getEntry(subFolderPath + "voltdb_files" + File.separator + "catalog.jar"); assertNotNull(catalogJar); ZipEntry deploymentXml = collectionZip.getEntry(subFolderPath + "voltdb_files" + File.separator + "deployment.xml"); assertNotNull(deploymentXml); ZipEntry systemCheck = collectionZip.getEntry(subFolderPath + "system_logs" + File.separator + "systemcheck"); assertNotNull(systemCheck); List<String> logPaths = getLogPaths(m_voltDbRootPath); for (String path : logPaths) { ZipEntry logFile = collectionZip.getEntry(subFolderPath + "voltdb_logs" + File.separator + new File(path).getName()); assertNotNull(logFile); } InputStream systemStatsIS; if (System.getProperty("os.name").contains("Mac")) systemStatsIS = new FileInputStream(getWorkingDir(m_voltDbRootPath)+"/lib/macstats.properties"); else systemStatsIS = new FileInputStream(getWorkingDir(m_voltDbRootPath)+"/lib/linuxstats.properties"); assertNotNull(systemStatsIS); Properties systemStats = new Properties(); systemStats.load(systemStatsIS); for (String fileName : systemStats.stringPropertyNames()) { ZipEntry statdata = collectionZip.getEntry(subFolderPath + "system_logs" + File.separator + fileName); assertNotNull(statdata); } Enumeration<? extends ZipEntry> e = collectionZip.entries(); while (e.hasMoreElements()) { String pathName = e.nextElement().getName(); if (pathName.startsWith(subFolderPath + "voltdb_crashfiles")) { assertTrue(pathName.startsWith(subFolderPath + "voltdb_crashfiles" + File.separator + "voltdb_crash") && pathName.endsWith(".txt")); } } } /* * For each type of file that need to be collected, check whether it actually appears in the collection * currently sar data and /var/log/syslog* are ignored in testing * since in some cluster machines sar is not enabled and syslog* can only be read by root */ @Test public void testBasicFilesAndCrash() throws Exception { //Terrible hack, wait for config logging thread to finish Thread.sleep(STARTUP_DELAY); try { client.callProcedure("CrashVoltDBProc"); } catch (Exception e) { } client.close(); cluster.shutDown(); // generate heap dump File heapdumpGenerated = new File("/tmp", "java_pid" + m_pid + ".hprof"); PrintWriter writer = new PrintWriter(heapdumpGenerated.getPath()); heapdumpGenerated.deleteOnExit(); writer.println("fake heapdump file"); writer.close(); File f = new File(m_voltDbRootPath, "systemcheck"); f.createNewFile(); FileOutputStream fStream = new FileOutputStream(f); fStream.write("fake text for test".getBytes()); fStream.close(); ZipFile collectionZip; m_outputFileName = new File(m_voltDbRootPath).getParent() + File.separator + m_pid + "_withCrash.zip"; deleteOutputFileIfExists(); collectionZip = collect(false, 50, false); verifyBasicTestCollect(collectionZip); collectionZip.close(); deleteOutputFileIfExists(); // negative test - prefix and output set at same time m_prefix = "foo_" + m_pid; boolean caughtExcp = false; try { collect(true, 3, false); } catch (SimulatedExitException excp) { System.out.println(excp.getMessage()); caughtExcp = true; } assertTrue(caughtExcp); m_outputFileName = ""; m_prefix = "prefix" + m_pid; collectionZip = collect(false, 3, true); verifyBasicTestCollect(collectionZip); collectionZip.close(); } @Test public void testJvmCrash() throws Exception { Thread.sleep(STARTUP_DELAY); try { client.callProcedure("CrashJVM"); } catch (Exception e) { } client.close(); cluster.shutDown(); m_outputFileName = new File(m_voltDbRootPath).getParent() + File.separator + m_pid + "_withJvmCrash.zip"; deleteOutputFileIfExists(); ZipFile collectionZip = collect(true, 50, false); String workingDir = getWorkingDir(m_voltDbRootPath); File jvmCrashGenerated = new File(workingDir, "hs_err_pid" + m_pid + ".log"); jvmCrashGenerated.deleteOnExit(); ZipEntry logFile = collectionZip.getEntry(m_collectBaseFolder + File.separator + "system_logs" + File.separator + "hs_err_pid" + m_pid + ".log"); assertNotNull(logFile); collectionZip.close(); } @Test public void testDaysToCollectOption() throws Exception { createLogFiles(); m_outputFileName = new File(m_voltDbRootPath).getParent() + File.separator + m_pid + "_withDaysToCollect.zip"; deleteOutputFileIfExists(); ZipFile collectionZip = collect(true, 3, false); int logCount = 0; Enumeration<? extends ZipEntry> e = collectionZip.entries(); while (e.hasMoreElements()) { ZipEntry z = e.nextElement(); if (z.getName().startsWith(m_collectBaseFolder + File.separator + "voltdb_logs" + File.separator)) logCount++; } assertEquals(logCount, 4); collectionZip.close(); } @Test public void testCollectFilesonYearBoundary() throws Exception { createLogFiles(); //set reference date to be 1st January of the current year Calendar cal = Calendar.getInstance(); cal.set(cal.get(Calendar.YEAR), 0, 01); Collector.m_currentTimeMillis = cal.getTimeInMillis(); resetCurrentTime = false; m_outputFileName = new File(m_voltDbRootPath).getParent() + File.separator + m_pid + "_withFilesOnYrBndry.zip"; ZipFile collectionZip = collect(true, 4, false); int logCount = 0; Enumeration<? extends ZipEntry> e = collectionZip.entries(); while (e.hasMoreElements()) { if (e.nextElement().getName().startsWith(m_collectBaseFolder + File.separator + "voltdb_logs" + File.separator)) logCount++; } assertEquals(logCount, 1); resetCurrentTime = true; collectionZip.close(); } @Test public void testRepeatFileName() throws Exception { createLogFiles(); m_outputFileName = new File(m_voltDbRootPath).getParent() + File.separator + m_pid + "_withRepeatedFileName.zip"; deleteOutputFileIfExists(); ZipFile collectionZip = collect(true, 3, false); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); ZipEntry repeatFile = collectionZip.getEntry(m_collectBaseFolder + File.separator + "voltdb_logs" + File.separator + "volt-junit-fulllog.txt." + formatter.format(new Date()) + "(1)"); assertNotNull(repeatFile); collectionZip.close(); } }