/* 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.sysprocs.saverestore; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterOutputStream; import org.apache.hadoop_voltpatches.util.PureJavaCrc32; import org.voltcore.utils.InstanceId; /** * Encapsulates knowledge about the storage format of the hashinator config snapshot file. */ public class HashinatorSnapshotData { /// Config data in serializeable/compressible format. public byte[] m_serData; /// Version number/transaction ID. public Long m_version; // Buffer positions. private static final int OFFSET_CRC = 0; private static final int OFFSET_INSTID_COORD = 8; private static final int OFFSET_INSTID_TIMESTAMP = 12; private static final int OFFSET_VERSION = 20; private static final int OFFSET_DATA = 28; /** * Constructor with data for save. * @param serData config data in compressed serializeable format * @param version version number/transaction ID */ public HashinatorSnapshotData(byte[] serData, long version) { m_serData = serData; m_version = version; } /** * Default constructor for restore. */ public HashinatorSnapshotData() { m_serData = null; m_version = null; } /** * Save to output buffer, including header and config data. * @return byte buffer ready to write to a file. * @throws I/O exception on failure */ public ByteBuffer saveToBuffer(InstanceId instId) throws IOException { if (instId == null) { throw new IOException("Null instance ID."); } if (m_serData == null) { throw new IOException("Uninitialized hashinator snapshot data."); } // Assume config data is the last field. ByteBuffer buf = ByteBuffer.allocate(m_serData.length + OFFSET_DATA); // Make sure the CRC starts at zero since those bytes figure into the CRC calculation. buf.putLong(OFFSET_CRC, 0); buf.putInt(OFFSET_INSTID_COORD, instId.getCoord()); buf.putLong(OFFSET_INSTID_TIMESTAMP, instId.getTimestamp()); buf.putLong(OFFSET_VERSION, m_version); buf.position(OFFSET_DATA); buf.put(m_serData); // Finalize the CRC based on the entire buffer and reset the current position. final PureJavaCrc32 crc = new PureJavaCrc32(); crc.update(buf.array()); buf.putLong(OFFSET_CRC, crc.getValue()); buf.rewind(); return buf; } /** * Restore and check hashinator config data. * @param buf input buffer * @return instance ID read from buffer * @throws I/O exception on failure */ public InstanceId restoreFromBuffer(ByteBuffer buf) throws IOException { buf.rewind(); // Assumes config data is the last field. int dataSize = buf.remaining() - OFFSET_DATA; if (dataSize <= 0) { throw new IOException("Hashinator snapshot data is too small."); } // Get the CRC, zero out its buffer field, and compare to calculated CRC. long crcHeader = buf.getLong(OFFSET_CRC); buf.putLong(OFFSET_CRC, 0); final PureJavaCrc32 crcBuffer = new PureJavaCrc32(); assert(buf.hasArray()); crcBuffer.update(buf.array()); if (crcHeader != crcBuffer.getValue()) { throw new IOException("Hashinator snapshot data CRC mismatch."); } // Slurp the data. int coord = buf.getInt(OFFSET_INSTID_COORD); long timestamp = buf.getLong(OFFSET_INSTID_TIMESTAMP); InstanceId instId = new InstanceId(coord, timestamp); m_version = buf.getLong(OFFSET_VERSION); m_serData = new byte[dataSize]; buf.position(OFFSET_DATA); buf.get(m_serData); return instId; } /** * Restore and check hashinator config data from a file. * @param file hashinator config file * @param return the buffer with the raw data * @throws IOException */ public void restoreFromFile(File file) throws IOException { byte[] rawData = new byte[(int) file.length()]; ByteBuffer bufData = null; FileInputStream fis = null; DataInputStream dis = null; try { fis = new FileInputStream(file); dis = new DataInputStream(fis); dis.readFully(rawData); bufData = ByteBuffer.wrap(rawData); restoreFromBuffer(bufData); } finally { if (dis != null) { dis.close(); } if (fis != null) { fis.close(); } } } }