-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Hadoop-17215. ABFS: Disable default create overwrite #2246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
12161d1
670ac1c
a55898b
0667067
ab088a5
f4ef740
6ff28f1
c4de926
bceadbd
70a17c1
5783bc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.hadoop.fs.azurebfs.contracts.exceptions; | ||
|
|
||
| /** | ||
| * Thrown when a concurrent write operation is detected. | ||
| */ | ||
| @org.apache.hadoop.classification.InterfaceAudience.Public | ||
| @org.apache.hadoop.classification.InterfaceStability.Evolving | ||
| public class ConcurrentWriteOperationDetectedException | ||
| extends AzureBlobFileSystemException { | ||
|
|
||
| public ConcurrentWriteOperationDetectedException(String message) { | ||
| super(message); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,8 @@ | |
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import org.apache.hadoop.fs.azurebfs.contracts.exceptions.AzureBlobFileSystemException; | ||
| import org.apache.hadoop.fs.azurebfs.contracts.exceptions.AbfsRestOperationException; | ||
| import org.apache.hadoop.fs.azurebfs.contracts.exceptions.ConcurrentWriteOperationDetectedException; | ||
| import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidUriException; | ||
| import org.apache.hadoop.fs.azurebfs.contracts.exceptions.SASTokenProviderException; | ||
| import org.apache.hadoop.fs.azurebfs.extensions.ExtensionHelper; | ||
|
|
@@ -263,10 +265,102 @@ public AbfsRestOperation deleteFilesystem() throws AzureBlobFileSystemException | |
| return op; | ||
| } | ||
|
|
||
| public AbfsRestOperation createPath(final String path, final boolean isFile, final boolean overwrite, | ||
| final String permission, final String umask, | ||
| final boolean isAppendBlob) throws AzureBlobFileSystemException { | ||
| public AbfsRestOperation createPath(final String path, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is reformatting of the previous code here that is unnecessary, but more importantly can cause merge conflicts when back porting and can introduce regressions if not carefully reviewed or caught by existing test automation. It is better to leave the old code as-is, but only update what must be updated. Just my thoughts.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only update to createPath in AbfsClient with the new iteration is the new eTag parameter now. Have tried to minimize code changes to existing code only where needed. |
||
| final boolean isFile, | ||
| final boolean overwrite, | ||
| final String permission, | ||
| final String umask, | ||
| final boolean isAppendBlob) throws AzureBlobFileSystemException { | ||
| String operation = isFile | ||
| ? SASTokenProvider.CREATE_FILE_OPERATION | ||
| : SASTokenProvider.CREATE_DIRECTORY_OPERATION; | ||
|
|
||
| // if "fs.azure.enable.conditional.create.overwrite" is enabled, | ||
| // trigger a create with overwrite=false first so that eTag fetch can be | ||
| // avoided for cases when no pre-existing file is present (which is the | ||
| // case with most part of create traffic) | ||
| boolean isFirstAttemptToCreateWithoutOverwrite = false; | ||
| if (isFile && overwrite | ||
| && abfsConfiguration.isConditionalCreateOverwriteEnabled()) { | ||
| isFirstAttemptToCreateWithoutOverwrite = true; | ||
| } | ||
|
|
||
| AbfsRestOperation op = null; | ||
| // Query builder | ||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_RESOURCE, | ||
| operation.equals(SASTokenProvider.CREATE_FILE_OPERATION) | ||
| ? FILE | ||
| : DIRECTORY); | ||
|
|
||
| if (isAppendBlob) { | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_BLOBTYPE, APPEND_BLOB_TYPE); | ||
| } | ||
|
|
||
| appendSASTokenToQuery(path, operation, abfsUriQueryBuilder); | ||
|
|
||
| try { | ||
| op = createPathImpl(path, abfsUriQueryBuilder, | ||
|
snvijaya marked this conversation as resolved.
Outdated
|
||
| (isFirstAttemptToCreateWithoutOverwrite ? false : overwrite), | ||
| permission, umask, null); | ||
| } catch (AbfsRestOperationException e) { | ||
| if ((e.getStatusCode() == HttpURLConnection.HTTP_CONFLICT) | ||
|
snvijaya marked this conversation as resolved.
Outdated
|
||
| && isFirstAttemptToCreateWithoutOverwrite) { | ||
| // Was the first attempt made to create file without overwrite which | ||
| // failed because there is a pre-existing file. | ||
| // resetting the first attempt flag for readabiltiy | ||
| isFirstAttemptToCreateWithoutOverwrite = false; | ||
|
|
||
| // Fetch eTag | ||
| try { | ||
| op = getPathStatus(path, false); | ||
| } catch (AbfsRestOperationException ex) { | ||
| if (ex.getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { | ||
| // Is a parallel access case, as file which was found to be | ||
| // present went missing by this request. | ||
| throw new ConcurrentWriteOperationDetectedException( | ||
| "Parallel access to the create path detected. Failing request " | ||
| + "to honor single writer semantics"); | ||
| } else { | ||
| throw ex; | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original design was such that AbfsClient is a thin client over the REST API, and AzureBlobFileSystemStore is where the app logic lived, such as handling continuation tokens or making multiple calls such as getting the etag to use it in a conditional request. I think we should stick to the original design and move this fancy logic to AzureBlobFileSystemStore and expose an option on AbfsClient to take an optional condition (the etag) when creating a file. In this way, the update to the AbfsClient would be a single line to add the optional "If-Match: E-Tag" request header, but there would be a new method in AzureBlobFileSystemStore that implements the new conditional overwrite behavior.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated PR with the recommendation. |
||
|
|
||
| String eTag = op.getResult().getResponseHeader(ETAG); | ||
|
|
||
| try { | ||
| // overwrite only if eTag matches with the file properties fetched befpre | ||
| op = createPathImpl(path, abfsUriQueryBuilder, true, permission, | ||
| umask, eTag); | ||
| } catch (AbfsRestOperationException ex) { | ||
| if (ex.getStatusCode() == HttpURLConnection.HTTP_PRECON_FAILED) { | ||
| // Is a parallel access case, as file with eTag was just queried | ||
| // and precondition failure can happen only when another file with | ||
| // different etag got created. | ||
| throw new ConcurrentWriteOperationDetectedException( | ||
| "Parallel access to the create path detected. Failing request " | ||
| + "to honor single writer semantics"); | ||
| } else { | ||
| throw ex; | ||
| } | ||
| } | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
|
|
||
| return op; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| public AbfsRestOperation createPathImpl(final String path, | ||
| AbfsUriQueryBuilder abfsUriQueryBuilder, | ||
| final boolean overwrite, | ||
| final String permission, | ||
| final String umask, | ||
| final String eTag) throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
|
|
||
| if (!overwrite) { | ||
| requestHeaders.add(new AbfsHttpHeader(IF_NONE_MATCH, AbfsHttpConstants.STAR)); | ||
| } | ||
|
|
@@ -279,17 +373,10 @@ public AbfsRestOperation createPath(final String path, final boolean isFile, fin | |
| requestHeaders.add(new AbfsHttpHeader(HttpHeaderConfigurations.X_MS_UMASK, umask)); | ||
| } | ||
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_RESOURCE, isFile ? FILE : DIRECTORY); | ||
| if (isAppendBlob) { | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_BLOBTYPE, APPEND_BLOB_TYPE); | ||
| if (eTag != null && !eTag.isEmpty()) { | ||
| requestHeaders.add(new AbfsHttpHeader(HttpHeaderConfigurations.IF_MATCH, eTag)); | ||
| } | ||
|
|
||
| String operation = isFile | ||
| ? SASTokenProvider.CREATE_FILE_OPERATION | ||
| : SASTokenProvider.CREATE_DIRECTORY_OPERATION; | ||
| appendSASTokenToQuery(path, operation, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = new AbfsRestOperation( | ||
| AbfsRestOperationType.CreatePath, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HDFS would simply acquire a lease and overwrite the file or fail to acquire the lease and throw an IOException, so what we're really pointing out here is the need for Azure Blob Storage to support lease atomically on file creation and for ABFS to use leases when writing to files so that it can uphold the single writer semantics. We knew this was needed from the beginning but the work has not been done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we should be doing something like https://issues.apache.org/jira/browse/HADOOP-16948 instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will check on what the scope for lease support is on server and how it can be extended to support this PR scenario when we plan for adopting the new lease related changes in server.