alluxio.master.file.meta.MountTable Maven / Gradle / Ivy
/*
* 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.file.meta;
import alluxio.AlluxioURI;
import alluxio.exception.AccessControlException;
import alluxio.exception.ExceptionMessage;
import alluxio.exception.FileAlreadyExistsException;
import alluxio.exception.InvalidPathException;
import alluxio.exception.status.NotFoundException;
import alluxio.exception.status.UnavailableException;
import alluxio.grpc.GrpcUtils;
import alluxio.grpc.MountPOptions;
import alluxio.master.file.meta.options.MountInfo;
import alluxio.master.journal.checkpoint.CheckpointName;
import alluxio.master.journal.DelegatingJournaled;
import alluxio.master.journal.JournalContext;
import alluxio.master.journal.Journaled;
import alluxio.proto.journal.File;
import alluxio.proto.journal.File.AddMountPointEntry;
import alluxio.proto.journal.File.DeleteMountPointEntry;
import alluxio.proto.journal.File.StringPairEntry;
import alluxio.proto.journal.Journal;
import alluxio.proto.journal.Journal.JournalEntry;
import alluxio.resource.CloseableResource;
import alluxio.resource.LockResource;
import alluxio.underfs.UfsManager;
import alluxio.underfs.UnderFileSystem;
import alluxio.util.IdUtils;
import alluxio.util.io.PathUtils;
import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
* This class is used for keeping track of Alluxio mount points.
*/
@ThreadSafe
public final class MountTable implements DelegatingJournaled {
private static final Logger LOG = LoggerFactory.getLogger(MountTable.class);
public static final String ROOT = "/";
private final Lock mReadLock;
private final Lock mWriteLock;
/** Mount table state that is preserved across restarts. */
@GuardedBy("mReadLock,mWriteLock")
private final State mState;
/** The manager of all ufs. */
private final UfsManager mUfsManager;
/**
* Creates a new instance of {@link MountTable}.
*
* @param ufsManager the UFS manager
* @param rootMountInfo root mount info
*/
public MountTable(UfsManager ufsManager, MountInfo rootMountInfo) {
mState = new State(rootMountInfo);
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
mReadLock = lock.readLock();
mWriteLock = lock.writeLock();
mUfsManager = ufsManager;
}
/**
* Mounts the given UFS path at the given Alluxio path. The Alluxio path should not be nested
* under an existing mount point.
*
* @param journalContext the journal context
* @param alluxioUri an Alluxio path URI
* @param ufsUri a UFS path URI
* @param mountId the mount id
* @param options the mount options
* @throws FileAlreadyExistsException if the mount point already exists
* @throws InvalidPathException if an invalid path is encountered
*/
public void add(Supplier journalContext, AlluxioURI alluxioUri, AlluxioURI ufsUri,
long mountId, MountPOptions options) throws FileAlreadyExistsException, InvalidPathException {
String alluxioPath = alluxioUri.getPath().isEmpty() ? "/" : alluxioUri.getPath();
LOG.info("Mounting {} at {}", ufsUri, alluxioPath);
try (LockResource r = new LockResource(mWriteLock)) {
if (mState.getMountTable().containsKey(alluxioPath)) {
throw new FileAlreadyExistsException(
ExceptionMessage.MOUNT_POINT_ALREADY_EXISTS.getMessage(alluxioPath));
}
// Make sure that the ufs path we're trying to mount is not a prefix
// or suffix of any existing mount path.
for (Map.Entry entry : mState.getMountTable().entrySet()) {
AlluxioURI mountedUfsUri = entry.getValue().getUfsUri();
if ((ufsUri.getScheme() == null || ufsUri.getScheme().equals(mountedUfsUri.getScheme()))
&& (ufsUri.getAuthority().toString().equals(mountedUfsUri.getAuthority().toString()))) {
String ufsPath = ufsUri.getPath().isEmpty() ? "/" : ufsUri.getPath();
String mountedUfsPath = mountedUfsUri.getPath().isEmpty() ? "/" : mountedUfsUri.getPath();
if (PathUtils.hasPrefix(ufsPath, mountedUfsPath)) {
throw new InvalidPathException(ExceptionMessage.MOUNT_POINT_PREFIX_OF_ANOTHER
.getMessage(mountedUfsUri.toString(), ufsUri.toString()));
}
if (PathUtils.hasPrefix(mountedUfsPath, ufsPath)) {
throw new InvalidPathException(ExceptionMessage.MOUNT_POINT_PREFIX_OF_ANOTHER
.getMessage(ufsUri.toString(), mountedUfsUri.toString()));
}
}
}
Map properties = options.getPropertiesMap();
mState.applyAndJournal(journalContext, AddMountPointEntry.newBuilder()
.addAllProperties(properties.entrySet().stream()
.map(entry -> StringPairEntry.newBuilder()
.setKey(entry.getKey()).setValue(entry.getValue()).build())
.collect(Collectors.toList()))
.setAlluxioPath(alluxioPath)
.setMountId(mountId)
.setReadOnly(options.getReadOnly())
.setShared(options.getShared())
.setUfsPath(ufsUri.toString())
.build());
}
}
/**
* Unmounts the given Alluxio path. The path should match an existing mount point.
*
* @param journalContext journal context
* @param uri an Alluxio path URI
* @param checkNestedMount whether to check nested mount points before delete
* @return whether the operation succeeded or not
*/
public boolean delete(Supplier journalContext, AlluxioURI uri,
boolean checkNestedMount) {
String path = uri.getPath();
LOG.info("Unmounting {}", path);
if (path.equals(ROOT)) {
LOG.warn("Cannot unmount the root mount point.");
return false;
}
try (LockResource r = new LockResource(mWriteLock)) {
if (mState.getMountTable().containsKey(path)) {
// check if the path contains another nested mount point
if (checkNestedMount) {
for (String mountPath : mState.getMountTable().keySet()) {
try {
if (PathUtils.hasPrefix(mountPath, path) && (!path.equals(mountPath))) {
LOG.warn("The path to unmount {} contains another nested mountpoint {}",
path, mountPath);
return false;
}
} catch (InvalidPathException e) {
LOG.warn("Invalid path {} encountered when checking for nested mount point", path);
}
}
}
mUfsManager.removeMount(mState.getMountTable().get(path).getMountId());
mState.applyAndJournal(journalContext,
DeleteMountPointEntry.newBuilder().setAlluxioPath(path).build());
return true;
}
LOG.warn("Mount point {} does not exist.", path);
return false;
}
}
/**
* Update the mount point with new options and mount ID.
*
* @param journalContext the journal context
* @param alluxioUri an Alluxio path URI
* @param newMountId the mount id
* @param newOptions the mount options
* @throws FileAlreadyExistsException if the mount point already exists
* @throws InvalidPathException if an invalid path is encountered
*/
public void update(Supplier journalContext, AlluxioURI alluxioUri,
long newMountId, MountPOptions newOptions) throws InvalidPathException,
FileAlreadyExistsException {
try (LockResource r = new LockResource(mWriteLock)) {
MountInfo mountInfo = getMountTable().get(alluxioUri.getPath());
if (mountInfo == null || !delete(journalContext, alluxioUri, false)) {
throw new InvalidPathException(String.format("Failed to update mount point at %s."
+ " Please ensure the path is an existing mount point and not root.",
alluxioUri.getPath()));
}
try {
add(journalContext, alluxioUri, mountInfo.getUfsUri(), newMountId, newOptions);
} catch (FileAlreadyExistsException | InvalidPathException e) {
// This should never happen since the path is guaranteed to exist and the mount point is
// just removed from the same path.
LOG.error("Failed to add the updated mount point at {}", alluxioUri, e);
// re-add old mount point
add(journalContext, alluxioUri, mountInfo.getUfsUri(), mountInfo.getMountId(),
mountInfo.getOptions());
throw e;
}
}
}
/**
* Returns the closest ancestor mount point the given path is nested under.
*
* @param uri an Alluxio path URI
* @return mount point the given Alluxio path is nested under
* @throws InvalidPathException if an invalid path is encountered
*/
public String getMountPoint(AlluxioURI uri) throws InvalidPathException {
String path = uri.getPath();
String lastMount = ROOT;
try (LockResource r = new LockResource(mReadLock)) {
for (Map.Entry entry : mState.getMountTable().entrySet()) {
String mount = entry.getKey();
// we choose a new candidate path if the previous candidatepath is a prefix
// of the current alluxioPath and the alluxioPath is a prefix of the path
if (!mount.equals(ROOT) && PathUtils.hasPrefix(path, mount)
&& PathUtils.hasPrefix(mount, lastMount)) {
lastMount = mount;
}
}
return lastMount;
}
}
/**
* Returns a copy of the current mount table, the mount table is a map from Alluxio file system
* URIs to the corresponding mount point information.
*
* @return a copy of the current mount table
*/
public Map getMountTable() {
try (LockResource r = new LockResource(mReadLock)) {
return new HashMap<>(mState.getMountTable());
}
}
/**
* @param uri the Alluxio uri to check
* @param containsSelf cause method to return true when given uri itself is a mount point
* @return true if the given uri has a descendant which is a mount point [, or is a mount point]
*/
public boolean containsMountPoint(AlluxioURI uri, boolean containsSelf)
throws InvalidPathException {
String path = uri.getPath();
try (LockResource r = new LockResource(mReadLock)) {
for (Map.Entry entry : mState.getMountTable().entrySet()) {
String mountPath = entry.getKey();
if (!containsSelf && mountPath.equals(path)) {
continue;
}
if (PathUtils.hasPrefix(mountPath, path)) {
return true;
}
}
}
return false;
}
/**
* @param uri an Alluxio path URI
* @return whether the given uri is a mount point
*/
public boolean isMountPoint(AlluxioURI uri) {
try (LockResource r = new LockResource(mReadLock)) {
return mState.getMountTable().containsKey(uri.getPath());
}
}
private AlluxioURI reverseResolve(AlluxioURI mountPoint,
AlluxioURI ufsUriMountPoint, AlluxioURI ufsUri)
throws InvalidPathException {
String relativePath = PathUtils.subtractPaths(
PathUtils.normalizePath(ufsUri.getPath(), AlluxioURI.SEPARATOR),
PathUtils.normalizePath(ufsUriMountPoint.getPath(), AlluxioURI.SEPARATOR));
if (relativePath.isEmpty()) {
return mountPoint;
} else {
return mountPoint.joinUnsafe(relativePath);
}
}
/**
* Resolves the given Ufs path. If the given UFs path is mounted in Alluxio space, it returns
* the associated Alluxio path.
* @param ufsUri an Ufs path URI
* @return an Alluxio path URI
*/
@Nullable
public ReverseResolution reverseResolve(AlluxioURI ufsUri) {
// TODO(ggezer): Consider alternative mount table representations for optimizing this method.
for (Map.Entry mountInfoEntry : getMountTable().entrySet()) {
try {
if (mountInfoEntry.getValue().getUfsUri().isAncestorOf(ufsUri)) {
return new ReverseResolution(mountInfoEntry.getValue(),
reverseResolve(mountInfoEntry.getValue().getAlluxioUri(),
mountInfoEntry.getValue().getUfsUri(), ufsUri));
}
} catch (InvalidPathException e) {
// expected when ufsUri does not belong to this particular mountPoint
LOG.debug(Throwables.getStackTraceAsString(e));
}
}
return null;
}
/**
* Get the associated ufs client with the mount id.
* @param mountId mount id to look up ufs client
* @return ufsClient
*/
@Nullable
public UfsManager.UfsClient getUfsClient(long mountId) {
try {
return mUfsManager.get(mountId);
} catch (NotFoundException | UnavailableException e) {
LOG.warn("failed to get ufsclient for mountid {}, exception {}", mountId, e);
}
return null;
}
/**
* Resolves the given Alluxio path. If the given Alluxio path is nested under a mount point, the
* resolution maps the Alluxio path to the corresponding UFS path. Otherwise, the resolution is a
* no-op.
*
* @param uri an Alluxio path URI
* @return the {@link Resolution} representing the UFS path
* @throws InvalidPathException if an invalid path is encountered
*/
public Resolution resolve(AlluxioURI uri) throws InvalidPathException {
try (LockResource r = new LockResource(mReadLock)) {
String path = uri.getPath();
LOG.debug("Resolving {}", path);
// This will re-acquire the read lock, but that is allowed.
String mountPoint = getMountPoint(uri);
if (mountPoint != null) {
MountInfo info = mState.getMountTable().get(mountPoint);
AlluxioURI ufsUri = info.getUfsUri();
UfsManager.UfsClient ufsClient;
AlluxioURI resolvedUri;
try {
ufsClient = mUfsManager.get(info.getMountId());
try (CloseableResource ufsResource = ufsClient.acquireUfsResource()) {
UnderFileSystem ufs = ufsResource.get();
resolvedUri = ufs.resolveUri(ufsUri, path.substring(mountPoint.length()));
}
} catch (NotFoundException | UnavailableException e) {
throw new RuntimeException(
String.format("No UFS information for %s for mount Id %d, we should never reach here",
uri, info.getMountId()), e);
}
return new Resolution(resolvedUri, ufsClient, info.getOptions().getShared(),
info.getMountId());
}
// TODO(binfan): throw exception as we should never reach here
return new Resolution(uri, null, false, IdUtils.INVALID_MOUNT_ID);
}
}
/**
* Checks to see if a write operation is allowed for the specified Alluxio path, by determining
* if it is under a readonly mount point.
*
* @param alluxioUri an Alluxio path URI
* @throws InvalidPathException if the Alluxio path is invalid
* @throws AccessControlException if the Alluxio path is under a readonly mount point
*/
public void checkUnderWritableMountPoint(AlluxioURI alluxioUri)
throws InvalidPathException, AccessControlException {
try (LockResource r = new LockResource(mReadLock)) {
// This will re-acquire the read lock, but that is allowed.
String mountPoint = getMountPoint(alluxioUri);
MountInfo mountInfo = mState.getMountTable().get(mountPoint);
if (mountInfo.getOptions().getReadOnly()) {
throw new AccessControlException(ExceptionMessage.MOUNT_READONLY, alluxioUri, mountPoint);
}
}
}
/**
* @param mountId the given ufs id
* @return the mount information with this id or null if this mount id is not found
*/
@Nullable
public MountInfo getMountInfo(long mountId) {
try (LockResource r = new LockResource(mReadLock)) {
for (Map.Entry entry : mState.getMountTable().entrySet()) {
if (entry.getValue().getMountId() == mountId) {
return entry.getValue();
}
}
}
return null;
}
@Override
public Journaled getDelegate() {
return mState;
}
/**
* This class represents a UFS path after resolution. The UFS URI and the {@link UnderFileSystem}
* for the UFS path are available.
*/
public final class Resolution {
private final AlluxioURI mUri;
private final UfsManager.UfsClient mUfsClient;
private final boolean mShared;
private final long mMountId;
private Resolution(AlluxioURI uri, UfsManager.UfsClient ufs, boolean shared, long mountId) {
mUri = uri;
mUfsClient = ufs;
mShared = shared;
mMountId = mountId;
}
/**
* @return the URI in the ufs
*/
public AlluxioURI getUri() {
return mUri;
}
/**
* @return the {@link UnderFileSystem} closeable resource
*/
public CloseableResource acquireUfsResource() {
return mUfsClient.acquireUfsResource();
}
/**
* @return the shared option
*/
public boolean getShared() {
return mShared;
}
/**
* @return the id of this mount point
*/
public long getMountId() {
return mMountId;
}
}
/**
* This class represents a Alluxio path after reverse resolution.
*/
public final class ReverseResolution {
private final MountInfo mMountInfo;
private final AlluxioURI mUri;
private ReverseResolution(MountInfo mountInfo, AlluxioURI uri) {
mMountInfo = mountInfo;
mUri = uri;
}
/**
* @return the URI in the Alluxio
*/
public AlluxioURI getUri() {
return mUri;
}
/**
* @return the {@link MountInfo} that resolved the URI
*/
public MountInfo getMountInfo() {
return mMountInfo;
}
}
/**
* Persistent mount table state. replayJournalEntryFromJournal should only be called during
* journal replay. To modify the mount table, create a journal entry and call one of the
* applyAndJournal methods.
*/
public static final class State implements Journaled {
/**
* Map from Alluxio path string to mount info.
*/
private final Map mMountTable;
/**
* @param mountInfo root mount info
*/
public State(MountInfo mountInfo) {
mMountTable = new HashMap<>(10);
mMountTable.put(MountTable.ROOT, mountInfo);
}
/**
* @return an unmodifiable view of the mount table
*/
public Map getMountTable() {
return Collections.unmodifiableMap(mMountTable);
}
/**
* @param context journal context
* @param entry add mount point entry
*/
public void applyAndJournal(Supplier context, AddMountPointEntry entry) {
applyAndJournal(context, JournalEntry.newBuilder().setAddMountPoint(entry).build());
}
/**
* @param context journal context
* @param entry delete mount point entry
*/
public void applyAndJournal(Supplier context, DeleteMountPointEntry entry) {
applyAndJournal(context, JournalEntry.newBuilder().setDeleteMountPoint(entry).build());
}
private void applyAddMountPoint(AddMountPointEntry entry) {
MountInfo mountInfo =
new MountInfo(new AlluxioURI(entry.getAlluxioPath()), new AlluxioURI(entry.getUfsPath()),
entry.getMountId(), GrpcUtils.fromMountEntry(entry));
mMountTable.put(entry.getAlluxioPath(), mountInfo);
}
private void applyDeleteMountPoint(DeleteMountPointEntry entry) {
mMountTable.remove(entry.getAlluxioPath());
}
@Override
public boolean processJournalEntry(JournalEntry entry) {
if (entry.hasAddMountPoint()) {
applyAddMountPoint(entry.getAddMountPoint());
} else if (entry.hasDeleteMountPoint()) {
applyDeleteMountPoint(entry.getDeleteMountPoint());
} else {
return false;
}
return true;
}
@Override
public void resetState() {
MountInfo mountInfo = mMountTable.get(ROOT);
mMountTable.clear();
if (mountInfo != null) {
mMountTable.put(ROOT, mountInfo);
}
}
@Override
public Iterator getJournalEntryIterator() {
final Iterator> it = mMountTable.entrySet().iterator();
return new Iterator() {
/** mEntry is always set to the next non-root mount point if exists. */
private Map.Entry mEntry = null;
@Override
public boolean hasNext() {
if (mEntry != null) {
return true;
}
if (it.hasNext()) {
mEntry = it.next();
// Skip the root mount point, which is considered a part of initial state, not journaled
// state.
if (mEntry.getKey().equals(ROOT)) {
mEntry = null;
return hasNext();
}
return true;
}
return false;
}
@Override
public Journal.JournalEntry next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String alluxioPath = mEntry.getKey();
MountInfo info = mEntry.getValue();
mEntry = null;
Map properties = info.getOptions().getProperties();
List protoProperties = new ArrayList<>(properties.size());
for (Map.Entry property : properties.entrySet()) {
protoProperties.add(File.StringPairEntry.newBuilder()
.setKey(property.getKey())
.setValue(property.getValue())
.build());
}
AddMountPointEntry addMountPoint =
AddMountPointEntry.newBuilder().setAlluxioPath(alluxioPath)
.setMountId(info.getMountId()).setUfsPath(info.getUfsUri().toString())
.setReadOnly(info.getOptions().getReadOnly()).addAllProperties(protoProperties)
.setShared(info.getOptions().getShared()).build();
return Journal.JournalEntry.newBuilder().setAddMountPoint(addMountPoint).build();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Mountable#Iterator#remove is not supported.");
}
};
}
@Override
public CheckpointName getCheckpointName() {
return CheckpointName.MOUNT_TABLE;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy