/* * Copyright (c) 2009 - 2011, Jan Stender, Bjoern Kolbeck, Mikael Hoegqvist, * Felix Hupfeld, Felix Langner, Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.babudb05.snapshots; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import de.mxro.thrd.babudb05.BabuDBRequestResultImpl; import de.mxro.thrd.babudb05.api.database.DatabaseRO; import de.mxro.thrd.babudb05.api.dev.BabuDBInternal; import de.mxro.thrd.babudb05.api.dev.DatabaseInternal; import de.mxro.thrd.babudb05.api.dev.SnapshotManagerInternal; import de.mxro.thrd.babudb05.api.dev.transaction.InMemoryProcessing; import de.mxro.thrd.babudb05.api.dev.transaction.OperationInternal; import de.mxro.thrd.babudb05.api.exception.BabuDBException; import de.mxro.thrd.babudb05.api.exception.BabuDBException.ErrorCode; import de.mxro.thrd.babudb05.api.transaction.Operation; import de.mxro.thrd.babudb05.lsmdb.BabuDBTransaction; import de.mxro.thrd.babudb05.lsmdb.InsertRecordGroup; import de.mxro.thrd.xstreemfs.foundation.buffer.ReusableBuffer; import de.mxro.thrd.xstreemfs.foundation.logging.Logging; import de.mxro.thrd.xstreemfs.foundation.util.FSUtils; public class SnapshotManagerImpl implements SnapshotManagerInternal { public static final String SNAP_DIR = "snapshots"; private final BabuDBInternal dbs; private final Map<String, Map<String, Snapshot>> snapshotDBs; public SnapshotManagerImpl(BabuDBInternal dbs) { this.dbs = dbs; this.snapshotDBs = Collections.synchronizedMap(new HashMap<String, Map<String, Snapshot>>()); initializeTransactionManager(); } public void init() throws BabuDBException { // load persisted snapshots from disk for (Entry<String, DatabaseInternal> entry : dbs.getDatabaseManager().getDatabasesInternal() .entrySet()) { final File snapDir = new File(dbs.getConfig().getBaseDir(), entry.getKey() + "/snapshots"); if (snapDir.exists()) { Map<String, Snapshot> snapMap = new HashMap<String, Snapshot>(); snapshotDBs.put(entry.getKey(), snapMap); boolean compressed = entry.getValue().getLSMDB().getIndex(0).isCompressed(); boolean mmaped = entry.getValue().getLSMDB().getIndex(0).isMMapEnabled(); String[] snapshots = snapDir.list(); for (String snapName : snapshots) { BabuDBView view = new DiskIndexView(snapDir + "/" + snapName, entry.getValue() .getComparators(), compressed, mmaped); snapMap.put(snapName, new Snapshot(view, dbs)); } } } } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.SnapshotManagerInternal#shutdown() */ @Override public void shutdown() throws BabuDBException { for (Map<String, Snapshot> snapshots : snapshotDBs.values()) for (Snapshot snapshot : snapshots.values()) snapshot.shutdown(); Logging.logMessage(Logging.LEVEL_DEBUG, this, "snapshot manager shut down successfully"); } @Override public DatabaseRO getSnapshotDB(String dbName, String snapshotName) throws BabuDBException { Map<String, Snapshot> snapMap = snapshotDBs.get(dbName); if (snapMap == null) throw new BabuDBException(ErrorCode.NO_SUCH_SNAPSHOT, "no snapshots exist for database '" + dbName + "'"); Snapshot snap = snapMap.get(snapshotName); if (snap == null) throw new BabuDBException(ErrorCode.NO_SUCH_SNAPSHOT, "no snapshot '" + snapshotName + "' exists for database '" + dbName + "'"); return snap; } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.SnapshotManager#createPersistentSnapshot(java.lang.String, org.xtreemfs.babudb.snapshots.SnapshotConfig) */ @Override public void createPersistentSnapshot(String dbName, SnapshotConfig snap) throws BabuDBException { // synchronously executing the request BabuDBRequestResultImpl<Object> result = new BabuDBRequestResultImpl<Object>(dbs.getResponseManager()); dbs.getTransactionManager().makePersistent( dbs.getDatabaseManager().createTransaction().createSnapshot(dbName, snap), result); result.get(); } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.SnapshotManagerInternal#snapshotComplete(java.lang.String, * org.xtreemfs.babudb.snapshots.SnapshotConfig) */ @Override public void snapshotComplete(String dbName, SnapshotConfig snap) throws BabuDBException { // as soon as the snapshot has been completed, replace the entry in the // snapshot DB map with a disk index-based BabuDB instance if necessary synchronized (snapshotDBs) { DatabaseInternal db = dbs.getDatabaseManager().getDatabase(dbName); boolean compressed = db.getLSMDB().getIndex(0).isCompressed(); boolean mmaped = db.getLSMDB().getIndex(0).isMMapEnabled(); Snapshot s = snapshotDBs.get(dbName).get(snap.getName()); s.setView(new DiskIndexView(getSnapshotDir(dbName, snap.getName()), dbs.getDatabaseManager() .getDatabase(dbName).getComparators(), compressed, mmaped)); } } @Override public void deletePersistentSnapshot(String dbName, String snapshotName) throws BabuDBException { BabuDBRequestResultImpl<Object> result = new BabuDBRequestResultImpl<Object>(dbs.getResponseManager()); dbs.getTransactionManager().makePersistent( dbs.getDatabaseManager().createTransaction().deleteSnapshot( dbName, snapshotName), result); result.get(); } @Override public String[] getAllSnapshots(String dbName) { final Map<String, Snapshot> snapMap = snapshotDBs.get(dbName); if (snapMap != null) { Set<String> names = snapMap.keySet(); return names.toArray(new String[names.size()]); } else return new String[0]; } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.SnapshotManagerInternal#deleteAllSnapshots(java.lang.String) */ @Override public void deleteAllSnapshots(String dbName) throws BabuDBException { final Map<String, Snapshot> snapMap = snapshotDBs.get(dbName); if (snapMap != null) { for (Entry<String, Snapshot> snap : snapMap.entrySet()) { // shut down the view snap.getValue().shutdown(); // if a snapshot materialization request is currently in the // checkpointer queue, remove it dbs.getCheckpointer().removeSnapshotMaterializationRequest(dbName, snap.getKey()); } // remove the map entry snapshotDBs.remove(dbName); } FSUtils.delTree(new File(getSnapshotDir(dbName, null))); // no delete log entries for the snapshots are needed here, since the // method will only be invoked when the database itself is deleted } /* (non-Javadoc) * @see org.xtreemfs.babudb.api.dev.SnapshotManagerInternal#getSnapshotDir(java.lang.String, * java.lang.String) */ @Override public String getSnapshotDir(String dbName, String snapshotName) { return dbs.getConfig().getBaseDir() + dbName + "/" + SnapshotManagerImpl.SNAP_DIR + "/" + (snapshotName == null ? "" : snapshotName); } /** * Feed the transactionManager with the knowledge on how to handle snapshot related requests. */ protected void initializeTransactionManager() { dbs.getTransactionManager().registerInMemoryProcessing(Operation.TYPE_CREATE_SNAP, new InMemoryProcessing() { @Override public Object[] deserializeRequest(ReusableBuffer serialized) throws BabuDBException { ObjectInputStream oin = null; try { oin = new ObjectInputStream(new ByteArrayInputStream(serialized.array())); int dbId = oin.readInt(); SnapshotConfig snap = (SnapshotConfig) oin.readObject(); return new Object[] { dbId, snap }; } catch (Exception e) { throw new BabuDBException(ErrorCode.IO_ERROR, "Could not deserialize operation of type " + Operation.TYPE_CREATE_SNAP + ", because: "+e.getMessage(), e); } finally { try { serialized.flip(); if (oin != null) oin.close(); } catch (IOException ioe) { /* who cares? */ } } } @Override public OperationInternal convertToOperation(Object[] args) { return new BabuDBTransaction.BabuDBOperation(Operation.TYPE_CREATE_SNAP, (String) null, new Object[] { args[0], args[1] }); } @Override public Object process(OperationInternal operation) throws BabuDBException { Object[] args = operation.getParams(); // parse args int dbId = (Integer) args[0]; SnapshotConfig snap = (SnapshotConfig) args[1]; // complete arguments if (dbId == InsertRecordGroup.DB_ID_UNKNOWN && operation.getDatabaseName() != null) { dbId = dbs.getDatabaseManager().getDatabase( operation.getDatabaseName()).getLSMDB().getDatabaseId(); operation.updateParams(new Object[] { dbId, snap }); } else if (operation.getDatabaseName() == null) { operation.updateDatabaseName( dbs.getDatabaseManager().getDatabase(dbId).getName()); } Map<String, Snapshot> snapMap = snapshotDBs.get(operation.getDatabaseName()); if (snapMap == null) { snapMap = new HashMap<String, Snapshot>(); snapshotDBs.put(operation.getDatabaseName(), snapMap); } // if the snapshot already exists ... if (snapMap.containsKey(snap.getName())) { throw new BabuDBException(ErrorCode.SNAP_EXISTS, "snapshot '" + snap.getName() + "' already exists"); } snapMap.put(snap.getName(), new Snapshot(null, dbs)); // first, create new in-memory snapshots of all indices int[] snapIds = null; try { dbs.getTransactionManager().lockService(); // create the snapshot snapIds = dbs.getDatabaseManager().getDatabase(dbId).getLSMDB().createSnapshot( snap.getIndices()); } catch (InterruptedException e) { throw new BabuDBException(ErrorCode.INTERRUPTED, e.getMessage()); } finally { dbs.getTransactionManager().unlockService(); } // then, enqueue a snapshot materialization request in the // checkpointer's queue dbs.getCheckpointer().addSnapshotMaterializationRequest(operation.getDatabaseName(), snapIds, snap); // as long as the snapshot has not been persisted yet, add a view on // the current snapshot in the original database to the snapshot DB map synchronized (snapshotDBs) { Snapshot s = snapMap.get(snap.getName()); if (s.getView() == null) { s.setView(new InMemoryView(dbs, operation.getDatabaseName(), snap, snapIds)); } } return null; } }); dbs.getTransactionManager().registerInMemoryProcessing(Operation.TYPE_DELETE_SNAP, new InMemoryProcessing() { @Override public Object[] deserializeRequest(ReusableBuffer serialized) throws BabuDBException { byte[] payload = serialized.array(); int offs = payload[0]; String dbName = new String(payload, 1, offs); String snapName = new String(payload, offs + 1, payload.length - offs - 1); serialized.flip(); return new Object[] { dbName, snapName }; } @Override public OperationInternal convertToOperation(Object[] args) { return new BabuDBTransaction.BabuDBOperation(Operation.TYPE_DELETE_SNAP, (String) args[0], new Object[] { args[1] }); } @Override public Object process(OperationInternal operation) throws BabuDBException { // parse args String snapshotName = (String) operation.getParams()[0]; final Map<String, Snapshot> snapMap = snapshotDBs.get(operation.getDatabaseName()); if(snapMap == null) { throw new BabuDBException(ErrorCode.NO_SUCH_SNAPSHOT, "snapshot '" + snapshotName + "' does not exist"); } final Snapshot snap = snapMap.get(snapshotName); // if the snapshot does not exist ... if (snap == null) { throw new BabuDBException(ErrorCode.NO_SUCH_SNAPSHOT, "snapshot '" + snapshotName + "' does not exist"); } // shut down and remove the view snap.getView().shutdown(); snapMap.remove(snapshotName); // if a snapshot materialization request is currently in the // checkpointer queue, remove it dbs.getCheckpointer().removeSnapshotMaterializationRequest( operation.getDatabaseName(), snapshotName); // delete the snapshot subdirectory on disk if available FSUtils.delTree(new File(getSnapshotDir(operation.getDatabaseName(), snapshotName))); return null; } }); } }