/*
* 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.util.URIUtils;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.util.UUID;
import javax.annotation.concurrent.ThreadSafe;
/**
* A simple data structure that represents a journal file.
* There are four types of files (SN is short for sequence number):
* 1. Temporary file: the file name is a random UUID.
* 2. Completed log file: [start SN]-[end SN] (the start SN is inclusive, the end SN is exclusive).
* 3. Incomplete log file (a.k.a the current log): [start SN]-[LONG.MAX_VALUE] (the start SN is
* inclusive).
* 4. Checkpoint file: [0]-[end SN] (the end SN is exclusive).
*
* This data structure implements {@link Comparable} such that journal files can be sorted by the
* end SNs.
*/
@ThreadSafe
final class UfsJournalFile implements Comparable<UfsJournalFile> {
private static final Logger LOG = LoggerFactory.getLogger(UfsJournalFile.class);
/** The location of the file. */
private final URI mLocation;
/** The start journal log entry sequence number (inclusive). */
private final long mStart;
/** The end journal log entry sequence number (exclusive). */
private final long mEnd;
/**
* Whether this is a checkpoint file (the temporary checkpoint file is not considered as a
* checkpoint file here).
*/
private final boolean mIsCheckpoint;
/**
* Creates a journal file.
*
* @param location the file location
* @param start the start sequence number (inclusive)
* @param end the end sequence number (exclusive)
* @param isCheckpoint whether this is a committed checkpoint file
*/
private UfsJournalFile(URI location, long start, long end, boolean isCheckpoint) {
mLocation = location;
mStart = start;
mEnd = end;
mIsCheckpoint = isCheckpoint;
}
/**
* Creates a committed checkpoint file.
*
* @param location the file location
* @param end the end sequence number (exclusive)
* @return the file
*/
static UfsJournalFile createCheckpointFile(URI location, long end) {
return new UfsJournalFile(location, 0, end, true);
}
/**
* Creates a journal log file.
*
* @param location the file location
* @param start the start sequence number (inclusive)
* @param end the end sequence number (exclusive)
* @return the file
*/
static UfsJournalFile createLogFile(URI location, long start, long end) {
return new UfsJournalFile(location, start, end, false);
}
/**
* Creates a temporary checkpoint file.
*
* @param location the file location
* @return the file
*/
static UfsJournalFile createTmpCheckpointFile(URI location) {
return new UfsJournalFile(location, UfsJournal.UNKNOWN_SEQUENCE_NUMBER,
UfsJournal.UNKNOWN_SEQUENCE_NUMBER, false);
}
/**
* Encodes a checkpoint location under the checkpoint directory.
*
* @param journal the UFS journal instance
* @param end the end sequence number (exclusive)
* @return the location
*/
static URI encodeCheckpointFileLocation(UfsJournal journal, long end) {
String filename = String.format("0x%x-0x%x", 0, end);
URI location = URIUtils.appendPathOrDie(journal.getCheckpointDir(), filename);
return location;
}
/**
* Encodes a log location under the log directory.
*
* @param journal the UFS journal instance
* @param start the start sequence number (inclusive)
* @param end the end sequence number (exclusive)
* @return the location
*/
static URI encodeLogFileLocation(UfsJournal journal, long start, long end) {
String filename = String.format("0x%x-0x%x", start, end);
URI location = URIUtils.appendPathOrDie(journal.getLogDir(), filename);
return location;
}
/**
* Encodes a temporary location under the temporary directory.
*
* @param journal the UFS journal instance*
* @return the location
*/
static URI encodeTemporaryCheckpointFileLocation(UfsJournal journal) {
return URIUtils.appendPathOrDie(journal.getTmpDir(), UUID.randomUUID().toString());
}
/**
* Decodes a checkpoint or a log file name into a {@link UfsJournalFile}.
*
* @param journal the UFS journal instance
* @param filename the filename
* @return the instance of {@link UfsJournalFile}, null if the file invalid
*/
static UfsJournalFile decodeLogFile(UfsJournal journal, String filename) {
URI location = URIUtils.appendPathOrDie(journal.getLogDir(), filename);
try {
String[] parts = filename.split("-");
// There can be temporary files in logs directory. Skip them.
if (parts.length != 2) {
return null;
}
long start = Long.decode(parts[0]);
long end = Long.decode(parts[1]);
return UfsJournalFile.createLogFile(location, start, end);
} catch (IllegalStateException e) {
LOG.error("Illegal journal file {}.", location);
throw e;
} catch (NumberFormatException e) {
// There can be temporary files (e.g. created for rename).
return null;
}
}
/**
* Decodes a checkpoint file name into a {@link UfsJournalFile}.
*
* @param journal the UFS journal instance
* @param filename the filename
* @return the instance of {@link UfsJournalFile}, null if the file invalid
*/
static UfsJournalFile decodeCheckpointFile(UfsJournal journal, String filename) {
URI location = URIUtils.appendPathOrDie(journal.getCheckpointDir(), filename);
try {
String[] parts = filename.split("-");
// There can be temporary files in logs directory. Skip them.
if (parts.length != 2) {
return null;
}
long start = Long.decode(parts[0]);
long end = Long.decode(parts[1]);
Preconditions.checkState(start == 0);
return UfsJournalFile.createCheckpointFile(location, end);
} catch (IllegalStateException e) {
LOG.error("Illegal journal file {}.", location);
throw e;
} catch (NumberFormatException e) {
// There can be temporary files (e.g. created for rename).
return null;
}
}
/**
* Decodes a temporary checkpoint file name into a {@link UfsJournalFile}.
*
* @param journal the UFS journal instance
* @param filename the temporary checkpoint file name
* @return the instance of {@link UfsJournalFile}
*/
static UfsJournalFile decodeTemporaryCheckpointFile(UfsJournal journal, String filename) {
URI location = URIUtils.appendPathOrDie(journal.getTmpDir(), filename);
return UfsJournalFile.createTmpCheckpointFile(location);
}
/**
* @return the file location
*/
URI getLocation() {
return mLocation;
}
/**
* @return the start sequence number (inclusive)
*/
long getStart() {
return mStart;
}
/**
* @return the end sequence number (exclusive)
*/
long getEnd() {
return mEnd;
}
/**
* @return whether it is a committed checkpoint file
*/
boolean isCheckpoint() {
return mIsCheckpoint;
}
/**
* @return whether it is a completed log
*/
boolean isCompletedLog() {
return !isCheckpoint() && mEnd != UfsJournal.UNKNOWN_SEQUENCE_NUMBER;
}
/**
* @return whether it is incomplete log (a.k.a. the current log)
*/
boolean isIncompleteLog() {
return mStart != UfsJournal.UNKNOWN_SEQUENCE_NUMBER
&& mEnd == UfsJournal.UNKNOWN_SEQUENCE_NUMBER;
}
/**
* @return whether it is a temporary checkpoint
*/
boolean isTmpCheckpoint() {
return mStart == UfsJournal.UNKNOWN_SEQUENCE_NUMBER
&& mEnd == UfsJournal.UNKNOWN_SEQUENCE_NUMBER;
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("location", mLocation).add("start", mStart)
.add("end", mEnd).add("isCheckpoint", mIsCheckpoint).toString();
}
@Override
public int compareTo(UfsJournalFile other) {
long diff = mEnd - other.mEnd;
if (diff < 0) {
return -1;
} else if (diff == 0) {
return 0;
} else {
return 1;
}
}
}