/* This file is part of VoltDB.
* Copyright (C) 2008-2017 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;
import java.net.URI;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.voltdb.sysprocs.saverestore.SnapshotPathType;
import org.voltdb.sysprocs.saverestore.SnapshotUtil;
/**
* Encapsulate the parameters provided to @SnapshotSave needed to initiate a snapshot.
* Handle parameter parsing, error generation, etc.
*/
public class SnapshotInitiationInfo
{
private String m_path;
private String m_nonce;
private boolean m_blocking;
private SnapshotFormat m_format;
private String m_data;
private boolean m_truncationRequest;
private SnapshotPathType m_stype;
private Long m_terminus = null;
public static final String MAGIC_NONCE_PREFIX = "MANUAL";
/**
* Construct the object given the parameters directly.
* @param data any additional JSON blob params. Currently only provided by VoltDB internals
*/
public SnapshotInitiationInfo(String path, String nonce, boolean blocking,
SnapshotFormat format, SnapshotPathType stype, String data)
{
m_path = path;
m_nonce = nonce;
m_blocking = blocking;
m_format = format;
m_data = data;
m_truncationRequest = false;
m_stype = stype;
m_terminus = null;
if (data != null && !data.trim().isEmpty()) {
try {
JSONObject jo = new JSONObject(data);
if (jo.has(SnapshotUtil.JSON_TERMINUS)) {
m_terminus = jo.getLong(SnapshotUtil.JSON_TERMINUS);
}
} catch (JSONException failedToFishForTerminus) {
}
}
}
/**
* Construct the object based on the params from the stored procedure invocation.
*/
public SnapshotInitiationInfo(Object[] params) throws Exception
{
m_path = null;
m_nonce = null;
m_blocking = true;
m_format = SnapshotFormat.NATIVE;
m_data = null;
m_truncationRequest = false;
boolean checkNonceValidity = true;
switch (params.length) {
case 3:
parseLegacyParams(params);
break;
case 1:
checkNonceValidity = parseJsonParams(params);
break;
case 0:
m_nonce = MAGIC_NONCE_PREFIX + System.currentTimeMillis();
m_stype = SnapshotPathType.SNAP_AUTO;
m_path = VoltDB.instance().getSnapshotPath();
m_blocking = false;
//We will always generate a good valid nonce.
checkNonceValidity = false;
break;
default:
throw new IllegalArgumentException("Invalid number of parameters, " + params.length + ". @SnapshotSave allows, 0, 1, or 3 parameters.");
}
if (checkNonceValidity && m_nonce != null && (m_nonce.contains("-") || m_nonce.contains(",") || m_nonce.startsWith(MAGIC_NONCE_PREFIX))) {
throw new IllegalArgumentException("Provided nonce " + m_nonce + " contains a prohibited character (- or ,) or starts with " + MAGIC_NONCE_PREFIX);
}
}
private void parseLegacyParams(Object[] params) throws Exception {
if (params[0] == null) {
throw new Exception("@SnapshotSave path is null");
}
if (params[1] == null) {
throw new Exception("@SnapshotSave nonce is null");
}
if (params[2] == null) {
throw new Exception("@SnapshotSave blocking is null");
}
if (!(params[0] instanceof String)) {
throw new Exception("@SnapshotSave path param is a " +
params[0].getClass().getSimpleName() +
" and should be a java.lang.String");
}
if (!(params[1] instanceof String)) {
throw new Exception("@SnapshotSave nonce param is a " +
params[0].getClass().getSimpleName() +
" and should be a java.lang.String");
}
if (!(params[2] instanceof Byte ||
params[2] instanceof Short ||
params[2] instanceof Integer ||
params[2] instanceof Long))
{
throw new Exception("@SnapshotSave blocking param is a " +
params[0].getClass().getSimpleName() +
" and should be a java.lang.[Byte|Short|Integer|Long]");
}
m_path = (String)params[0];
m_nonce = (String)params[1];
m_blocking = ((Number)params[2]).byteValue() == 0 ? false : true;
m_format = SnapshotFormat.NATIVE;
m_stype = SnapshotPathType.SNAP_PATH;
}
/**
* Parse the JSON blob. Schema is roughly:
* keys:
* (optional) service: currently only 'log_truncation' is valid. Will
* induce a command log truncation snapshot if command logging is present
* and enabled. This will cause all other keys to be unnecessary and/or
* ignored.
*
* uripath: the URI where the snapshot should be written to. Only file:// URIs are currently accepted.
*
* nonce: the nonce to be used for the snapshot. '-' and ',' are forbidden nonce characters
*
* block: whether or not the snapshot should block the database or not
* while it's being generated. All non-zero numbers will be interpreted
* as blocking. true/false will be interpreted as you'd expect
*
* format: one of 'native' or 'csv'.
*/
private boolean parseJsonParams(Object[] params) throws Exception
{
boolean checkValidity = true;
if (params[0] == null) {
throw new Exception("@SnapshotSave JSON blob is null");
}
if (!(params[0] instanceof String)) {
throw new Exception("@SnapshotSave JSON blob is a " +
params[0].getClass().getSimpleName() +
" and should be a java.lang.String");
}
final JSONObject jsObj = new JSONObject((String)params[0]);
// IZZY - Make service an enum and store it in the object if
// we every introduce another one
if (jsObj.has(SnapshotUtil.JSON_SERVICE)) {
String service = jsObj.getString(SnapshotUtil.JSON_SERVICE);
if (service.equalsIgnoreCase("log_truncation")) {
m_truncationRequest = true;
if (!VoltDB.instance().getCommandLog().isEnabled()) {
throw new Exception("Cannot ask for a command log truncation snapshot when " +
"command logging is not present or enabled.");
}
// for CL truncation, don't care about any of the rest of the blob.
return checkValidity;
}
else {
throw new Exception("Unknown snapshot save service type: " + service);
}
}
m_stype = SnapshotPathType.valueOf(jsObj.optString(SnapshotUtil.JSON_PATH_TYPE, SnapshotPathType.SNAP_PATH.toString()));
if (jsObj.has(SnapshotUtil.JSON_URIPATH)) {
m_path = jsObj.getString(SnapshotUtil.JSON_URIPATH);
if (m_path.isEmpty()) {
throw new Exception("uripath cannot be empty");
}
} else {
m_stype = SnapshotPathType.SNAP_AUTO;
m_path = "file:///" + VoltDB.instance().getCommandLogSnapshotPath();
}
URI pathURI = new URI(m_path);
String pathURIScheme = pathURI.getScheme();
if (pathURIScheme == null) {
throw new Exception("URI scheme cannot be null");
}
if (!pathURIScheme.equals("file")) {
throw new Exception("Unsupported URI scheme " + pathURIScheme +
" if this is a file path then you must prepend file://");
}
m_path = pathURI.getPath();
if (jsObj.has(SnapshotUtil.JSON_NONCE)) {
m_nonce = jsObj.getString(SnapshotUtil.JSON_NONCE);
if (m_nonce.isEmpty()) {
throw new Exception("nonce cannot be empty");
}
} else {
m_nonce = MAGIC_NONCE_PREFIX + System.currentTimeMillis();
//This is a valid JSON
checkValidity = false;
}
Object blockingObj = false;
if (jsObj.has(SnapshotUtil.JSON_BLOCK)) {
blockingObj = jsObj.get(SnapshotUtil.JSON_BLOCK);
}
if (blockingObj instanceof Number) {
m_blocking = ((Number)blockingObj).byteValue() == 0 ? false : true;
} else if (blockingObj instanceof Boolean) {
m_blocking = (Boolean)blockingObj;
} else if (blockingObj instanceof String) {
m_blocking = Boolean.valueOf((String)blockingObj);
} else {
throw new Exception(blockingObj.getClass().getName() + " is not supported as " +
" type for the block parameter");
}
m_format = SnapshotFormat.NATIVE;
String formatString = jsObj.optString(SnapshotUtil.JSON_FORMAT,SnapshotFormat.NATIVE.toString());
m_terminus = jsObj.has(SnapshotUtil.JSON_TERMINUS) ? jsObj.getLong(SnapshotUtil.JSON_TERMINUS) : null;
/*
* Try and be very flexible about what we will accept
* as the type of the block parameter.
*/
try {
m_format = SnapshotFormat.getEnumIgnoreCase(formatString);
} catch (IllegalArgumentException argException) {
throw new Exception("@SnapshotSave format param is a " + m_format +
" and should be one of [\"native\" | \"csv\"]");
}
m_data = (String)params[0];
return checkValidity;
}
public String getPath()
{
return m_path;
}
public String getNonce()
{
return m_nonce;
}
public boolean isBlocking()
{
return m_blocking;
}
public SnapshotFormat getFormat()
{
return m_format;
}
public boolean isTruncationRequest()
{
return m_truncationRequest;
}
public String getJSONBlob()
{
return m_data;
}
public Long getTerminus()
{
return m_terminus;
}
/**
* When we write to ZK to request the snapshot, generate the JSON which will be written to the node's data.
*/
public JSONObject getJSONObjectForZK() throws JSONException
{
final JSONObject jsObj = new JSONObject();
jsObj.put(SnapshotUtil.JSON_PATH, m_path);
jsObj.put(SnapshotUtil.JSON_PATH_TYPE, m_stype.toString());
jsObj.put(SnapshotUtil.JSON_NONCE, m_nonce);
jsObj.put(SnapshotUtil.JSON_BLOCK, m_blocking);
jsObj.put(SnapshotUtil.JSON_FORMAT, m_format.toString());
jsObj.putOpt(SnapshotUtil.JSON_DATA, m_data);
jsObj.putOpt(SnapshotUtil.JSON_TERMINUS, m_terminus);
return jsObj;
}
}