Java tutorial
/* This file is part of VoltDB. * Copyright (C) 2008-2013 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.sysprocs; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.json_voltpatches.JSONArray; import org.json_voltpatches.JSONObject; import org.json_voltpatches.JSONStringer; import org.voltcore.logging.VoltLogger; import org.voltcore.utils.CoreUtils; import org.voltdb.DependencyPair; import org.voltdb.ParameterSet; import org.voltdb.ProcInfo; import org.voltdb.SnapshotFormat; import org.voltdb.SnapshotSaveAPI; import org.voltdb.SnapshotSiteProcessor; import org.voltdb.SystemProcedureExecutionContext; import org.voltdb.VoltSystemProcedure; import org.voltdb.VoltTable; import org.voltdb.VoltTable.ColumnInfo; import org.voltdb.VoltType; import org.voltdb.catalog.Table; import org.voltdb.dtxn.DtxnConstants; import org.voltdb.iv2.MpInitiator; import org.voltdb.iv2.TxnEgo; import org.voltdb.sysprocs.saverestore.SnapshotUtil; import org.voltdb.utils.VoltTableUtil; import com.google.common.primitives.Longs; @ProcInfo(singlePartition = false) public class SnapshotSave extends VoltSystemProcedure { private static final VoltLogger TRACE_LOG = new VoltLogger(SnapshotSave.class.getName()); private static final VoltLogger SNAP_LOG = new VoltLogger("SNAPSHOT"); private static final int DEP_saveTest = (int) SysProcFragmentId.PF_saveTest | DtxnConstants.MULTIPARTITION_DEPENDENCY; private static final int DEP_saveTestResults = (int) SysProcFragmentId.PF_saveTestResults; private static final int DEP_createSnapshotTargets = (int) SysProcFragmentId.PF_createSnapshotTargets | DtxnConstants.MULTIPARTITION_DEPENDENCY; private static final int DEP_createSnapshotTargetsResults = (int) SysProcFragmentId.PF_createSnapshotTargetsResults; private static final int DEP_snapshotSaveQuiesce = (int) SysProcFragmentId.PF_snapshotSaveQuiesce | DtxnConstants.MULTIPARTITION_DEPENDENCY; private static final int DEP_snapshotSaveQuiesceResults = (int) SysProcFragmentId.PF_snapshotSaveQuiesceResults; public static final ColumnInfo nodeResultsColumns[] = new ColumnInfo[] { new ColumnInfo(CNAME_HOST_ID, CTYPE_ID), new ColumnInfo("HOSTNAME", VoltType.STRING), new ColumnInfo("TABLE", VoltType.STRING), new ColumnInfo("RESULT", VoltType.STRING), new ColumnInfo("ERR_MSG", VoltType.STRING) }; public static final ColumnInfo partitionResultsColumns[] = new ColumnInfo[] { new ColumnInfo(CNAME_HOST_ID, CTYPE_ID), new ColumnInfo("HOSTNAME", VoltType.STRING), new ColumnInfo(CNAME_SITE_ID, CTYPE_ID), new ColumnInfo("RESULT", VoltType.STRING), new ColumnInfo("ERR_MSG", VoltType.STRING) }; public static final VoltTable constructNodeResultsTable() { return new VoltTable(nodeResultsColumns); } public static final VoltTable constructPartitionResultsTable() { return new VoltTable(partitionResultsColumns); } @Override public void init() { registerPlanFragment(SysProcFragmentId.PF_saveTest); registerPlanFragment(SysProcFragmentId.PF_saveTestResults); registerPlanFragment(SysProcFragmentId.PF_createSnapshotTargets); registerPlanFragment(SysProcFragmentId.PF_createSnapshotTargetsResults); registerPlanFragment(SysProcFragmentId.PF_snapshotSaveQuiesce); registerPlanFragment(SysProcFragmentId.PF_snapshotSaveQuiesceResults); } @Override public DependencyPair executePlanFragment(Map<Integer, List<VoltTable>> dependencies, long fragmentId, ParameterSet params, SystemProcedureExecutionContext context) { String hostname = CoreUtils.getHostnameOrAddress(); if (fragmentId == SysProcFragmentId.PF_saveTest) { assert (params.toArray()[0] != null); assert (params.toArray()[1] != null); assert (params.toArray()[2] != null); String file_path = (String) params.toArray()[0]; String file_nonce = (String) params.toArray()[1]; SnapshotFormat format = SnapshotFormat.getEnumIgnoreCase((String) params.toArray()[2]); String data = (String) params.toArray()[3]; return saveTest(file_path, file_nonce, format, data, context, hostname); } else if (fragmentId == SysProcFragmentId.PF_saveTestResults) { VoltTable result = VoltTableUtil.unionTables(dependencies.get(DEP_saveTest)); return new DependencyPair(DEP_saveTestResults, result); } else if (fragmentId == SysProcFragmentId.PF_createSnapshotTargets) { assert (params.toArray()[0] != null); assert (params.toArray()[1] != null); assert (params.toArray()[2] != null); assert (params.toArray()[3] != null); assert (params.toArray()[4] != null); assert (params.toArray()[5] != null); assert (params.toArray()[6] != null); assert (params.toArray()[7] != null); final String file_path = (String) params.toArray()[0]; final String file_nonce = (String) params.toArray()[1]; final long txnId = (Long) params.toArray()[2]; long perPartitionTxnIds[] = (long[]) params.toArray()[3]; byte block = (Byte) params.toArray()[4]; SnapshotFormat format = SnapshotFormat.getEnumIgnoreCase((String) params.toArray()[5]); /* * Filter out the partitions that are active in the cluster * and include values for all partitions that aren't part of the current cluster. * These transaction ids are used to track partitions that have come and gone * so that the ids can resume without duplicates if the partitions are brought back. */ List<Long> perPartitionTransactionIdsToKeep = new ArrayList<Long>(); for (long txnid : perPartitionTxnIds) { int partitionId = TxnEgo.getPartitionId(txnid); if (partitionId >= context.getNumberOfPartitions() && partitionId != MpInitiator.MP_INIT_PID) { perPartitionTransactionIdsToKeep.add(txnid); } } String data = (String) params.toArray()[6]; final long timestamp = (Long) params.toArray()[7]; SnapshotSaveAPI saveAPI = new SnapshotSaveAPI(); VoltTable result = saveAPI.startSnapshotting(file_path, file_nonce, format, block, txnId, context.getLastCommittedSpHandle(), Longs.toArray(perPartitionTransactionIdsToKeep), data, context, hostname, timestamp); return new DependencyPair(SnapshotSave.DEP_createSnapshotTargets, result); } else if (fragmentId == SysProcFragmentId.PF_createSnapshotTargetsResults) { VoltTable result = VoltTableUtil.unionTables(dependencies.get(DEP_createSnapshotTargets)); return new DependencyPair(DEP_createSnapshotTargetsResults, result); } else if (fragmentId == SysProcFragmentId.PF_snapshotSaveQuiesce) { // tell each site to quiesce context.getSiteProcedureConnection().quiesce(); VoltTable results = new VoltTable(new ColumnInfo("id", VoltType.BIGINT)); results.addRow(context.getSiteId()); return new DependencyPair(DEP_snapshotSaveQuiesce, results); } else if (fragmentId == SysProcFragmentId.PF_snapshotSaveQuiesceResults) { VoltTable dummy = new VoltTable(VoltSystemProcedure.STATUS_SCHEMA); dummy.addRow(VoltSystemProcedure.STATUS_OK); return new DependencyPair(DEP_snapshotSaveQuiesceResults, dummy); } assert (false); return null; } private DependencyPair saveTest(String file_path, String file_nonce, SnapshotFormat format, String data, SystemProcedureExecutionContext context, String hostname) { { VoltTable result = constructNodeResultsTable(); // Choose the lowest site ID on this host to do the file scan // All other sites should just return empty results tables. if (context.isLowestSiteId()) { TRACE_LOG.trace( "Checking feasibility of save with path and nonce: " + file_path + ", " + file_nonce); final int numSitesSnapshotting = SnapshotSiteProcessor.ExecutionSitesCurrentlySnapshotting.size(); if (numSitesSnapshotting > 0) { SNAP_LOG.debug( "Snapshot in progress, " + numSitesSnapshotting + " sites are still snapshotting"); result.addRow(context.getHostId(), hostname, "", "FAILURE", "SNAPSHOT IN PROGRESS"); return new DependencyPair(DEP_saveTest, result); } for (Table table : SnapshotUtil.getTablesToSave(context.getDatabase())) { String file_valid = "SUCCESS"; String err_msg = ""; if (format.isFileBased()) { File saveFilePath = SnapshotUtil.constructFileForTable(table, file_path, file_nonce, format, context.getHostId()); TRACE_LOG.trace("Host ID " + context.getHostId() + " table: " + table.getTypeName() + " to path: " + saveFilePath); if (saveFilePath.exists()) { file_valid = "FAILURE"; err_msg = "SAVE FILE ALREADY EXISTS: " + saveFilePath; } else if (!saveFilePath.getParentFile().canWrite()) { file_valid = "FAILURE"; err_msg = "FILE LOCATION UNWRITABLE: " + saveFilePath; } else { try { /* * Sanity check that the file can be created * and then delete it so empty files aren't * orphaned if another part of the snapshot * test fails. */ if (saveFilePath.createNewFile()) { saveFilePath.delete(); } } catch (IOException ex) { file_valid = "FAILURE"; err_msg = "FILE CREATION OF " + saveFilePath + "RESULTED IN IOException: " + ex.getMessage(); } } } result.addRow(context.getHostId(), hostname, table.getTypeName(), file_valid, err_msg); } } return new DependencyPair(DEP_saveTest, result); } } public VoltTable[] run(SystemProcedureExecutionContext ctx, String command) throws Exception { final long startTime = System.currentTimeMillis(); JSONObject jsObj = new JSONObject(command); final boolean block = jsObj.optBoolean("block", false); final String async = !block ? "Asynchronously" : "Synchronously"; final String path = jsObj.getString("path"); final String nonce = jsObj.getString("nonce"); String formatStr = jsObj.optString("format", SnapshotFormat.NATIVE.toString()); final SnapshotFormat format = SnapshotFormat.getEnumIgnoreCase(formatStr); final String data = jsObj.optString("data"); JSONArray perPartitionTransactionIdsArray = jsObj.optJSONArray("perPartitionTxnIds"); if (perPartitionTransactionIdsArray == null) { /* * Not going to make this fatal because I don't want people to * be blocked from getting their data out via snapshots. */ SNAP_LOG.error("Failed to retrieve per partition transaction ids array from SnapshotDaemon." + "This shouldn't happen and it prevents the snapshot from including transaction ids " + "for partitions that are no longer active in the cluster. Those ids are necessary " + "to prevent those partitions from generating duplicate unique ids when they are brought back."); perPartitionTransactionIdsArray = new JSONArray(); } long perPartitionTxnIds[] = new long[perPartitionTransactionIdsArray.length()]; for (int ii = 0; ii < perPartitionTxnIds.length; ii++) { perPartitionTxnIds[ii] = perPartitionTransactionIdsArray.getLong(ii); } if (format == SnapshotFormat.STREAM) { SNAP_LOG.info(async + " streaming database, ID: " + nonce + " at " + startTime); } else { SNAP_LOG.info(async + " saving database to path: " + path + ", ID: " + nonce + " at " + startTime); } ColumnInfo[] error_result_columns = new ColumnInfo[2]; int ii = 0; error_result_columns[ii++] = new ColumnInfo("RESULT", VoltType.STRING); error_result_columns[ii++] = new ColumnInfo("ERR_MSG", VoltType.STRING); if (format.isFileBased() && (path == null || path.equals(""))) { VoltTable results[] = new VoltTable[] { new VoltTable(error_result_columns) }; results[0].addRow("FAILURE", "Provided path was null or the empty string"); return results; } if (nonce == null || nonce.equals("")) { VoltTable results[] = new VoltTable[] { new VoltTable(error_result_columns) }; results[0].addRow("FAILURE", "Provided nonce was null or the empty string"); return results; } if (nonce.contains("-") || nonce.contains(",")) { VoltTable results[] = new VoltTable[] { new VoltTable(error_result_columns) }; results[0].addRow("FAILURE", "Provided nonce " + nonce + " contains a prohibited character (- or ,)"); return results; } // See if we think the save will succeed VoltTable[] results; results = performSaveFeasibilityWork(path, nonce, format, data); // Test feasibility results for fail while (results[0].advanceRow()) { if (results[0].getString("RESULT").equals("FAILURE")) { // Something lost, bomb out and just return the whole // table of results to the client for analysis results[0].resetRowPosition(); return results; } } performQuiesce(); results = performSnapshotCreationWork(path, nonce, ctx.getCurrentTxnId(), perPartitionTxnIds, (byte) (block ? 1 : 0), format, data); SnapshotSaveAPI.logParticipatingHostCount(ctx.getCurrentTxnId()); try { JSONStringer stringer = new JSONStringer(); stringer.object(); stringer.key("txnId").value(ctx.getCurrentTxnId()); stringer.endObject(); setAppStatusString(stringer.toString()); } catch (Exception e) { SNAP_LOG.warn(e); } final long finishTime = System.currentTimeMillis(); final long duration = finishTime - startTime; SNAP_LOG.info("Snapshot initiation took " + duration + " milliseconds"); return results; } private final VoltTable[] performSaveFeasibilityWork(String filePath, String fileNonce, SnapshotFormat format, String data) { SynthesizedPlanFragment[] pfs = new SynthesizedPlanFragment[2]; // This fragment causes each execution site to confirm the likely // success of writing tables to disk pfs[0] = new SynthesizedPlanFragment(); pfs[0].fragmentId = SysProcFragmentId.PF_saveTest; pfs[0].outputDepId = DEP_saveTest; pfs[0].inputDepIds = new int[] {}; pfs[0].multipartition = true; pfs[0].parameters = ParameterSet.fromArrayNoCopy(filePath, fileNonce, format.name(), data); // This fragment aggregates the save-to-disk sanity check results pfs[1] = new SynthesizedPlanFragment(); pfs[1].fragmentId = SysProcFragmentId.PF_saveTestResults; pfs[1].outputDepId = DEP_saveTestResults; pfs[1].inputDepIds = new int[] { DEP_saveTest }; pfs[1].multipartition = false; pfs[1].parameters = ParameterSet.emptyParameterSet(); VoltTable[] results; results = executeSysProcPlanFragments(pfs, DEP_saveTestResults); return results; } private final VoltTable[] performSnapshotCreationWork(String filePath, String fileNonce, long txnId, long perPartitionTxnIds[], byte block, SnapshotFormat format, String data) { SynthesizedPlanFragment[] pfs = new SynthesizedPlanFragment[2]; // This fragment causes each execution node to create the files // that will be written to during the snapshot pfs[0] = new SynthesizedPlanFragment(); pfs[0].fragmentId = SysProcFragmentId.PF_createSnapshotTargets; pfs[0].outputDepId = DEP_createSnapshotTargets; pfs[0].inputDepIds = new int[] {}; pfs[0].multipartition = true; pfs[0].parameters = ParameterSet.fromArrayNoCopy(filePath, fileNonce, txnId, perPartitionTxnIds, block, format.name(), data, System.currentTimeMillis()); // This fragment aggregates the results of creating those files pfs[1] = new SynthesizedPlanFragment(); pfs[1].fragmentId = SysProcFragmentId.PF_createSnapshotTargetsResults; pfs[1].outputDepId = DEP_createSnapshotTargetsResults; pfs[1].inputDepIds = new int[] { DEP_createSnapshotTargets }; pfs[1].multipartition = false; pfs[1].parameters = ParameterSet.emptyParameterSet(); VoltTable[] results; results = executeSysProcPlanFragments(pfs, DEP_createSnapshotTargetsResults); return results; } private final VoltTable[] performQuiesce() { SynthesizedPlanFragment[] pfs = new SynthesizedPlanFragment[2]; // This fragment causes each execution site flush export // data to disk with a sync pfs[0] = new SynthesizedPlanFragment(); pfs[0].fragmentId = SysProcFragmentId.PF_snapshotSaveQuiesce; pfs[0].outputDepId = DEP_snapshotSaveQuiesce; pfs[0].inputDepIds = new int[] {}; pfs[0].multipartition = true; pfs[0].parameters = ParameterSet.emptyParameterSet(); // This fragment aggregates the quiesce results pfs[1] = new SynthesizedPlanFragment(); pfs[1].fragmentId = SysProcFragmentId.PF_snapshotSaveQuiesceResults; pfs[1].outputDepId = DEP_snapshotSaveQuiesceResults; pfs[1].inputDepIds = new int[] { DEP_snapshotSaveQuiesce }; pfs[1].multipartition = false; pfs[1].parameters = ParameterSet.emptyParameterSet(); VoltTable[] results; results = executeSysProcPlanFragments(pfs, DEP_snapshotSaveQuiesceResults); return results; } }