Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
109a0ba
changed signature to bring it to redo method
saxenapranav Jul 20, 2023
e585777
flowing etag in renameAtomicUtils to write to JSON; read from JSON
saxenapranav Jul 24, 2023
1ad1c02
etag check before redo
saxenapranav Jul 24, 2023
3156efd
redo method doesn't need etag; tests wip
saxenapranav Jul 24, 2023
5563827
added tests
saxenapranav Jul 24, 2023
08606b4
quote or unquote the etag
saxenapranav Jul 24, 2023
38b6dfa
javadocs changes
saxenapranav Jul 24, 2023
77ec128
try saving listStatus call if nothing changed
saxenapranav Jul 24, 2023
bab6499
asssertion if the listStatus API of FS will give correct result when …
saxenapranav Jul 24, 2023
1dd8204
added a comment on leaseAcquire catch in redo method
saxenapranav Jul 25, 2023
3d04da3
renamePendingJSON in listStatus to be searched only if its atomicDire…
saxenapranav Jul 25, 2023
d9a38b8
added tests; to take qualified path uri for checking if its atomic in…
saxenapranav Jul 25, 2023
fbf0a24
Merge branch 'ABFS_3.3.2_dev' into ABFS_3.3.2_dev_redo_optimization_r…
saxenapranav Jul 25, 2023
14104c1
line spacing refactor
saxenapranav Jul 25, 2023
a660122
HADOOP-17682. ABFS: Support FileStatus input to OpenFileWithOptions()…
sumangala-patki Aug 18, 2021
08b8c79
listStatus and getFileStatus will open the inputStream and send for r…
saxenapranav Jul 26, 2023
aa2d21d
modified time to check the number of invocation of getFileStatus in t…
saxenapranav Jul 26, 2023
e69321e
directly calling store.openWithOptions from listStatus/getFileStatus;…
saxenapranav Jul 26, 2023
9be288b
testRefator
saxenapranav Jul 26, 2023
f326e52
Merge branch 'ABFS_3.3.2_dev' into ABFS_3.3.2_dev_redo_optimization_f…
saxenapranav Jul 26, 2023
dd12da4
for fileStatus of renamePendingJson, we will only call getPathProperty.
saxenapranav Jul 26, 2023
549285d
invocation of getPathProperty instead of getFileStatus
saxenapranav Jul 26, 2023
b76972e
fix tests in ITestAzureBlobFileSystemFileStatus
saxenapranav Jul 27, 2023
1f77da6
fileStatus.getpath().toUri().getPathh() extract variable for reuse
saxenapranav Jul 27, 2023
680f18c
filePathStr = qualifiedPath.toUri().getPath() instead of path.toUri()…
saxenapranav Jul 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidConfigurationValueException;
import org.apache.hadoop.fs.azurebfs.services.AbfsBlobLease;
import org.apache.hadoop.fs.azurebfs.services.BlobProperty;
Expand Down Expand Up @@ -116,6 +117,7 @@
import static org.apache.hadoop.fs.CommonConfigurationKeys.IOSTATISTICS_LOGGING_LEVEL_DEFAULT;
import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.*;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.BLOB_LEASE_ONE_MINUTE_DURATION;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.FORWARD_SLASH;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ENABLE_BLOB_ENDPOINT;
import static org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.ABFS_DNS_PREFIX;
import static org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.WASB_DNS_PREFIX;
Expand Down Expand Up @@ -928,15 +930,35 @@ public FileStatus[] listStatus(final Path f) throws IOException {
listener);
FileStatus[] result = getAbfsStore().listStatus(qualifiedPath, tracingContext);
if (getAbfsStore().getAbfsConfiguration().getPrefixMode()
== PrefixMode.BLOB) {
FileStatus renamePendingFileStatus
== PrefixMode.BLOB && getAbfsStore().isAtomicRenameKey(
qualifiedPath.toUri().getPath() + FORWARD_SLASH)) {
Pair<FileStatus, FileStatus> renamePendingJsonAndSrcFileStatusPair
= getAbfsStore().getRenamePendingFileStatus(result);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename variable to renamePendingJSONStatus

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored.

if (renamePendingFileStatus != null) {
RenameAtomicityUtils renameAtomicityUtils =
getRenameAtomicityUtilsForRedo(renamePendingFileStatus.getPath(),
tracingContext);
renameAtomicityUtils.cleanup(renamePendingFileStatus.getPath());
result = getAbfsStore().listStatus(qualifiedPath, tracingContext);
FileStatus renamePendingSrcFileStatus
= renamePendingJsonAndSrcFileStatusPair.getRight();
FileStatus renamePendingJsonFileStatus
= renamePendingJsonAndSrcFileStatusPair.getLeft();
if (renamePendingJsonFileStatus != null) {
final Boolean isRedone;
if (renamePendingSrcFileStatus != null) {
RenameAtomicityUtils renameAtomicityUtils =
getRenameAtomicityUtilsForRedo(
renamePendingJsonFileStatus.getPath(),
tracingContext,
((AzureBlobFileSystemStore.VersionedFileStatus) renamePendingSrcFileStatus).getEtag());
renameAtomicityUtils.cleanup(renamePendingJsonFileStatus.getPath());
isRedone = renameAtomicityUtils.isRedone();
} else {
isRedone = false;
getAbfsStore().delete(renamePendingJsonFileStatus.getPath(), true,
tracingContext);
}
if (isRedone) {
result = getAbfsStore().listStatus(qualifiedPath, tracingContext);
} else {
result = ArrayUtils.removeElement(result,
renamePendingJsonFileStatus);
}
}
}
return result;
Expand All @@ -947,10 +969,10 @@ public FileStatus[] listStatus(final Path f) throws IOException {
}

RenameAtomicityUtils getRenameAtomicityUtilsForRedo(final Path renamePendingFileStatus,
final TracingContext tracingContext) throws IOException {
final TracingContext tracingContext, final String srcEtag) throws IOException {
return new RenameAtomicityUtils(this,
renamePendingFileStatus,
getAbfsStore().getRedoRenameInvocation(tracingContext));
getAbfsStore().getRedoRenameInvocation(tracingContext), srcEtag);
}

/**
Expand Down Expand Up @@ -1083,15 +1105,20 @@ private FileStatus getFileStatus(final Path path,
&& getAbfsStore().isAtomicRenameKey(fileStatus.getPath().toUri().getPath())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as prev comment, resume only if this qualifies as parent of atomic rename path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current code already has the check.

&& getAbfsStore().getRenamePendingFileStatusInDirectory(fileStatus,
tracingContext)) {
RenameAtomicityUtils renameAtomicityUtils = getRenameAtomicityUtilsForRedo(
new Path(fileStatus.getPath().toUri().getPath() + SUFFIX),
tracingContext);
renameAtomicityUtils.cleanup(
new Path(fileStatus.getPath().toUri().getPath() + SUFFIX));
throw new AbfsRestOperationException(HttpURLConnection.HTTP_NOT_FOUND,
AzureServiceErrorCode.PATH_NOT_FOUND.getErrorCode(), null,
new FileNotFoundException(
qualifiedPath + ": No such file or directory."));
RenameAtomicityUtils renameAtomicityUtils
= getRenameAtomicityUtilsForRedo(
new Path(fileStatus.getPath().toUri().getPath() + SUFFIX),
tracingContext,
((AzureBlobFileSystemStore.VersionedFileStatus) fileStatus).getEtag());
renameAtomicityUtils.cleanup(
new Path(fileStatus.getPath().toUri().getPath() + SUFFIX));
if (renameAtomicityUtils.isRedone()) {
throw new AbfsRestOperationException(
HttpURLConnection.HTTP_NOT_FOUND,
AzureServiceErrorCode.PATH_NOT_FOUND.getErrorCode(), null,
new FileNotFoundException(
qualifiedPath + ": No such file or directory."));
}
}
return fileStatus;
} catch (AzureBlobFileSystemException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.hadoop.classification.VisibleForTesting;

import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidConfigurationValueException;
Expand Down Expand Up @@ -681,6 +682,7 @@ BlobProperty getBlobProperty(Path blobPath,
blobProperty.setCopyStatus(opResult.getResponseHeader(X_MS_COPY_STATUS));
blobProperty.setContentLength(
Long.parseLong(opResult.getResponseHeader(CONTENT_LENGTH)));
blobProperty.setETag(extractEtagHeader(opResult));
return blobProperty;
}

Expand Down Expand Up @@ -1027,7 +1029,7 @@ private AbfsRestOperation createFileOrMarker(boolean isNormalBlob, String relati
}

// Fallback plan : default to v1 create flow which will hit dfs endpoint. Config to enable: "fs.azure.ingress.fallback.to.dfs".
public OutputStream createFile(final Path path, final FileSystem.Statistics statistics, final boolean overwrite,
public AbfsOutputStream createFile(final Path path, final FileSystem.Statistics statistics, final boolean overwrite,
final FsPermission permission, final FsPermission umask,
TracingContext tracingContext, HashMap<String, String> metadata) throws IOException {
try (AbfsPerfInfo perfInfo = startTracking("createFile", "createPath")) {
Expand Down Expand Up @@ -1241,7 +1243,7 @@ private AbfsOutputStreamContext populateAbfsOutputStreamContext(
.build();
}

public void createDirectory(final Path path, final FileSystem.Statistics statistics, final FsPermission permission,
public String createDirectory(final Path path, final FileSystem.Statistics statistics, final FsPermission permission,
final FsPermission umask,
final Boolean checkParentChain,
TracingContext tracingContext)
Expand All @@ -1256,13 +1258,14 @@ public void createDirectory(final Path path, final FileSystem.Statistics statist
}
boolean blobOverwrite = abfsConfiguration.isEnabledBlobMkdirOverwrite();

createDirectoryMarkerBlob(path, statistics, permission, umask, tracingContext,
AbfsOutputStream pathDirectoryOutputStream = createDirectoryMarkerBlob(
path, statistics, permission, umask, tracingContext,
blobOverwrite);
for (Path pathToCreate: keysToCreateAsFolder) {
createDirectoryMarkerBlob(pathToCreate, statistics, permission, umask,
tracingContext, blobOverwrite);
}
return;
return pathDirectoryOutputStream.getETag();
}
boolean isNamespaceEnabled = getIsNamespaceEnabled(tracingContext);
LOG.debug("Mkdir created via dfs endpoint for the given path {} and config value {} ",
Expand All @@ -1282,18 +1285,19 @@ public void createDirectory(final Path path, final FileSystem.Statistics statist
isNamespaceEnabled ? getOctalNotation(umask) : null, false, null,
tracingContext);
perfInfo.registerResult(op.getResult()).registerSuccess(true);
return op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
}
}

private void createDirectoryMarkerBlob(final Path path,
private AbfsOutputStream createDirectoryMarkerBlob(final Path path,
final FileSystem.Statistics statistics,
final FsPermission permission,
final FsPermission umask,
final TracingContext tracingContext,
final boolean blobOverwrite) throws IOException {
HashMap<String, String> metadata = new HashMap<>();
metadata.put(X_MS_META_HDI_ISFOLDER, TRUE);
createFile(path, statistics, blobOverwrite,
return createFile(path, statistics, blobOverwrite,
permission, umask, tracingContext, metadata);
}

Expand Down Expand Up @@ -1632,20 +1636,22 @@ private void orchestrateBlobRenameDir(final Path source,
blobPropOnSrcNullable = null;
}

final String srcDirETag;
if (blobPropOnSrcNullable == null) {
/*
* There is no marker-blob, the client has to create marker blob before
* starting the rename.
*/
LOG.debug("Source {} is a directory but there is no marker-blob",
source);
createDirectory(source, null, FsPermission.getDirDefault(),
srcDirETag = createDirectory(source, null, FsPermission.getDirDefault(),
FsPermission.getUMask(
getAbfsConfiguration().getRawConfiguration()),
true, tracingContext);
} else {
LOG.debug("Source {} is a directory but there is a marker-blob",
source);
srcDirETag = blobPropOnSrcNullable.getETag();
}
/*
* If source is a directory, all the blobs in the directory have to be
Expand All @@ -1661,7 +1667,7 @@ private void orchestrateBlobRenameDir(final Path source,
BLOB_LEASE_ONE_MINUTE_DURATION,
tracingContext);
renameAtomicityUtils.preRename(
isCreateOperationOnBlobEndpoint());
isCreateOperationOnBlobEndpoint(), srcDirETag);
isAtomicRename = true;
} else {
srcDirLease = null;
Expand Down Expand Up @@ -2828,8 +2834,21 @@ public void redo(final Path destination, final Path src)
}
String listSrc = listSrcBuilder.toString();
getListBlobProducer(listSrc, listBlobQueue, null, tracingContext);
AbfsBlobLease abfsBlobLease = getBlobLease(src.toUri().getPath(),
BLOB_LEASE_ONE_MINUTE_DURATION, tracingContext);
final AbfsBlobLease abfsBlobLease;
try {
abfsBlobLease = getBlobLease(src.toUri().getPath(),
BLOB_LEASE_ONE_MINUTE_DURATION, tracingContext);
} catch (AbfsRestOperationException ex) {
/*
* The required blob might be deleted in between the last check (from
* GetFileStatus or ListStatus) and the leaseAcquire. Hence, catching
* HTTP_NOT_FOUND error.
*/
if (ex.getStatusCode() == HTTP_NOT_FOUND) {
return;
}
throw ex;
}
renameBlobDir(src, destination, tracingContext, listBlobQueue,
abfsBlobLease, true);
}
Expand Down Expand Up @@ -3051,17 +3070,28 @@ private AbfsPerfInfo startTracking(String callerName, String calleeName) {
}

/**
* Search for a FileStatus corresponding to a RenamePending JSON file.
* @param fileStatuses array of fileStatus from which JSON file has to be searched.
* @return filestatus corresponding to RenamePending JSON file.
* Returns a pair of fileStatuses for the renamePendingJSON file and the renameSource file.
* @param fileStatuses array of fileStatus from which pair has to be searched.
* @return Pair of FileStatus. Left of the pair is fileStatus of renamePendingJson file.
* Right of the pair is fileStatus of renameSource file.
*/
public FileStatus getRenamePendingFileStatus(final FileStatus[] fileStatuses) {
public Pair<FileStatus, FileStatus> getRenamePendingFileStatus(final FileStatus[] fileStatuses) {
Map<String, FileStatus> fileStatusMap = new HashMap<>();
FileStatus renamePendingJsonFileStatus = null;
String requiredRenameSrcPath = null;
for (FileStatus fileStatus : fileStatuses) {
if (fileStatus.getPath().toUri().getPath().endsWith(SUFFIX)) {
return fileStatus;
String path = fileStatus.getPath().toUri().getPath();
if (path.equals(requiredRenameSrcPath)) {
return Pair.of(renamePendingJsonFileStatus, fileStatus);
}
fileStatusMap.put(path, fileStatus);
if (path.endsWith(SUFFIX)) {
renamePendingJsonFileStatus = fileStatus;
requiredRenameSrcPath = path.split(SUFFIX)[0];
}
}
return null;
return Pair.of(renamePendingJsonFileStatus,
fileStatusMap.get(requiredRenameSrcPath));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
/**
* For a directory enabled for atomic-rename, before rename starts, a
* file with -RenamePending.json suffix is created. In this file, the states required
* for the rename are given. This file is created by {@link #preRename(Boolean)} ()} method.
* for the rename are given. This file is created by {@link #preRename(Boolean, String)} ()} method.
* This is important in case the JVM process crashes during rename, the atomicity
* will be maintained, when the job calls {@link AzureBlobFileSystem#listStatus(Path)}
* or {@link AzureBlobFileSystem#getFileStatus(Path)}. On these API calls to filesystem,
Expand All @@ -66,6 +66,7 @@ public class RenameAtomicityUtils {
private Path srcPath;
private Path dstPath;
private TracingContext tracingContext;
private Boolean isReDone;

private static final int MAX_RENAME_PENDING_FILE_SIZE = 10000000;
private static final int FORMATTING_BUFFER = 10000;
Expand All @@ -87,13 +88,18 @@ public RenameAtomicityUtils(final AzureBlobFileSystem azureBlobFileSystem,
}

public RenameAtomicityUtils(final AzureBlobFileSystem azureBlobFileSystem,
final Path path, final RedoRenameInvocation redoRenameInvocation)
final Path path, final RedoRenameInvocation redoRenameInvocation,
final String srcEtag)
throws IOException {
this.azureBlobFileSystem = azureBlobFileSystem;
final RenamePendingFileInfo renamePendingFileInfo = readFile(path);
if (renamePendingFileInfo != null) {
if (renamePendingFileInfo != null
&& renamePendingFileInfo.eTag.equalsIgnoreCase(srcEtag)) {
redoRenameInvocation.redo(renamePendingFileInfo.destination,
renamePendingFileInfo.src);
isReDone = true;
} else {
isReDone = false;
}
}

Expand Down Expand Up @@ -148,13 +154,17 @@ private RenamePendingFileInfo readFile(final Path redoFile)
// initialize this object's fields
JsonNode oldFolderName = json.get("OldFolderName");
JsonNode newFolderName = json.get("NewFolderName");
JsonNode eTag = json.get("ETag");

if (oldFolderName != null && StringUtils.isNotEmpty(
oldFolderName.textValue())
&& newFolderName != null && StringUtils.isNotEmpty(
newFolderName.textValue())) {
newFolderName.textValue()) && eTag != null && StringUtils.isNotEmpty(
eTag.textValue())) {
RenamePendingFileInfo renamePendingFileInfo = new RenamePendingFileInfo();
renamePendingFileInfo.destination = new Path(newFolderName.textValue());
renamePendingFileInfo.src = new Path(oldFolderName.textValue());
renamePendingFileInfo.eTag = eTag.textValue();
return renamePendingFileInfo;
}
return null;
Expand Down Expand Up @@ -186,6 +196,7 @@ private void deleteRenamePendingFile(FileSystem fs, Path redoFile)
* OperationTime: "<YYYY-MM-DD HH:MM:SS.MMM>",
* OldFolderName: "<key>",
* NewFolderName: "<key>"
* ETag: "<etag of the src-directory>"
* }
*
* Here's a sample:
Expand All @@ -194,15 +205,17 @@ private void deleteRenamePendingFile(FileSystem fs, Path redoFile)
* OperationUTCTime: "2014-07-01 23:50:35.572",
* OldFolderName: "user/ehans/folderToRename",
* NewFolderName: "user/ehans/renamedFolder"
* ETag: "ETag"
* } }</pre>
* @throws IOException Thrown when fail to write file.
*/
public void preRename(final Boolean isCreateOperationOnBlobEndpoint) throws IOException {
public void preRename(final Boolean isCreateOperationOnBlobEndpoint,
final String eTag) throws IOException {
Path path = getRenamePendingFilePath();
LOG.debug("Preparing to write atomic rename state to {}", path.toString());
OutputStream output = null;

String contents = makeRenamePendingFileContents();
String contents = makeRenamePendingFileContents(eTag);

// Write file.
try {
Expand Down Expand Up @@ -261,18 +274,22 @@ private Throwable getWrappedException(final IOException e) {
*
* @return JSON string which represents the operation.
*/
private String makeRenamePendingFileContents() {
private String makeRenamePendingFileContents(String eTag) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String time = sdf.format(new Date());
if(!eTag.startsWith("\"") && !eTag.endsWith("\"")) {
eTag = quote(eTag);
}

// Make file contents as a string. Again, quote file names, escaping
// characters as appropriate.
String contents = "{\n"
+ " FormatVersion: \"1.0\",\n"
+ " OperationUTCTime: \"" + time + "\",\n"
+ " OldFolderName: " + quote(srcPath.toUri().getPath()) + ",\n"
+ " NewFolderName: " + quote(dstPath.toUri().getPath()) + "\n"
+ " NewFolderName: " + quote(dstPath.toUri().getPath()) + ",\n"
+ " ETag: " + eTag + "\n"
+ "}\n";

return contents;
Expand Down Expand Up @@ -366,10 +383,15 @@ private Path getRenamePendingFilePath() {
private static class RenamePendingFileInfo {
public Path destination;
public Path src;
public String eTag;
}

public static interface RedoRenameInvocation {
void redo(Path destination, Path src) throws
AzureBlobFileSystemException;
}

public Boolean isRedone() {
return isReDone;
}
}
Loading