/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.master.journal.ufs; import alluxio.Configuration; import alluxio.PropertyKey; import alluxio.master.journal.Journal; import alluxio.master.journal.JournalReader; import alluxio.master.journal.JournalWriter; import alluxio.master.journal.options.JournalReaderOptions; import alluxio.master.journal.options.JournalWriterOptions; import alluxio.underfs.UfsStatus; import alluxio.underfs.UnderFileSystem; import alluxio.underfs.options.DeleteOptions; import alluxio.util.URIUtils; import alluxio.util.UnderFileSystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URI; import javax.annotation.concurrent.ThreadSafe; /** * Implementation of UFS-based journal. * * The journal is made up of 2 components: * - The checkpoint: a snapshot of the master state * - The log entries: incremental entries to apply to the checkpoint. * * The journal log entries must be self-contained. Checkpoint is considered as a compaction of * a set of journal log entries. If the master does not do any checkpoint, the journal should * still be sufficient. * * Journal file structure: * journal_folder/version/logs/StartSequenceNumber-EndSequenceNumber * journal_folder/version/checkpoints/0-EndSequenceNumber * journal_folder/version/.tmp/random_id */ @ThreadSafe public class UfsJournal implements Journal { private static final Logger LOG = LoggerFactory.getLogger(UfsJournal.class); /** * This is set to Long.MAX_VALUE such that the current log can be sorted after any other * completed logs. */ public static final long UNKNOWN_SEQUENCE_NUMBER = Long.MAX_VALUE; /** The journal version. */ public static final String VERSION = "v1"; /** Directory for journal edit logs including the incomplete log file. */ private static final String LOG_DIRNAME = "logs"; /** Directory for committed checkpoints. */ private static final String CHECKPOINT_DIRNAME = "checkpoints"; /** Directory for temporary files. */ private static final String TMP_DIRNAME = ".tmp"; private final URI mLogDir; private final URI mCheckpointDir; private final URI mTmpDir; /** The location where this journal is stored. */ private final URI mLocation; /** The UFS where the journal is being written to. */ private final UnderFileSystem mUfs; /** * Creates a new instance of {@link UfsJournal}. * * @param location the location for this journal */ public UfsJournal(URI location) { this(location, UnderFileSystem.Factory.create(location)); } /** * Creates a new instance of {@link UfsJournal}. * * @param location the location for this journal * @param ufs the under file system */ UfsJournal(URI location, UnderFileSystem ufs) { mLocation = URIUtils.appendPathOrDie(location, VERSION); mUfs = ufs; mLogDir = URIUtils.appendPathOrDie(mLocation, LOG_DIRNAME); mCheckpointDir = URIUtils.appendPathOrDie(mLocation, CHECKPOINT_DIRNAME); mTmpDir = URIUtils.appendPathOrDie(mLocation, TMP_DIRNAME); } @Override public URI getLocation() { return mLocation; } @Override public JournalReader getReader(JournalReaderOptions options) { return new UfsJournalReader(this, options); } @Override public JournalWriter getWriter(JournalWriterOptions options) throws IOException { if (options.isPrimary()) { return new UfsJournalLogWriter(this, options); } else { return new UfsJournalCheckpointWriter(this, options); } } @Override public long getNextSequenceNumberToCheckpoint() throws IOException { return UfsJournalSnapshot.getNextLogSequenceNumberToCheckpoint(this); } @Override public boolean isFormatted() throws IOException { UfsStatus[] files = mUfs.listStatus(mLocation.toString()); if (files == null) { return false; } // Search for the format file. String formatFilePrefix = Configuration.get(PropertyKey.MASTER_FORMAT_FILE_PREFIX); for (UfsStatus file : files) { if (file.getName().startsWith(formatFilePrefix)) { return true; } } return false; } @Override public void format() throws IOException { URI location = getLocation(); LOG.info("Formatting {}", location); if (mUfs.isDirectory(location.toString())) { for (UfsStatus status : mUfs.listStatus(location.toString())) { String childPath = URIUtils.appendPathOrDie(location, status.getName()).toString(); if (status.isDirectory() && !mUfs.deleteDirectory(childPath, DeleteOptions.defaults().setRecursive(true)) || status.isFile() && !mUfs.deleteFile(childPath)) { throw new IOException(String.format("Failed to delete %s", childPath)); } } } else if (!mUfs.mkdirs(location.toString())) { throw new IOException(String.format("Failed to create %s", location)); } // Create a breadcrumb that indicates that the journal folder has been formatted. UnderFileSystemUtils.touch(mUfs, URIUtils.appendPathOrDie(location, Configuration.get(PropertyKey.MASTER_FORMAT_FILE_PREFIX) + System.currentTimeMillis()) .toString()); } /** * @return the log directory location */ URI getLogDir() { return mLogDir; } /** * @return the checkpoint directory location */ URI getCheckpointDir() { return mCheckpointDir; } /** * @return the temporary directory location */ URI getTmpDir() { return mTmpDir; } /** * @return the under file system instance */ UnderFileSystem getUfs() { return mUfs; } }