Skip to content

Commit 914b99c

Browse files
authored
HDDS-14968. Concurrent S3 Conditional PUT Commit Conflict Detection (#10043)
1 parent 4d8c38d commit 914b99c

9 files changed

Lines changed: 34 additions & 10 deletions

File tree

hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,5 +279,7 @@ public enum ResultCodes {
279279
ETAG_MISMATCH,
280280

281281
ETAG_NOT_AVAILABLE,
282+
283+
ATOMIC_WRITE_CONFLICT,
282284
}
283285
}

hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/OzoneRpcClientTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
4343
import static org.apache.hadoop.ozone.client.OzoneClientTestUtils.assertKeyContent;
4444
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL;
45+
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.ATOMIC_WRITE_CONFLICT;
46+
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.ETAG_MISMATCH;
4547
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_ALREADY_EXISTS;
4648
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_NOT_FOUND;
4749
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR;
@@ -1424,7 +1426,7 @@ void rewriteFailsDueToOutdatedGenerationAtCommit(BucketLayout layout) throws IOE
14241426
keyInfo = ozoneManager.lookupKey(keyArgs);
14251427

14261428
OMException e = assertThrows(OMException.class, out::close);
1427-
assertEquals(KEY_NOT_FOUND, e.getResult());
1429+
assertEquals(ATOMIC_WRITE_CONFLICT, e.getResult());
14281430
assertThat(e).hasMessageContaining("does not match the expected generation to rewrite");
14291431
} finally {
14301432
if (out != null) {
@@ -1561,7 +1563,7 @@ void testRewriteKeyIfMatchFailsWithWrongETag(BucketLayout layout) throws IOExcep
15611563
out.write(newContent);
15621564
}
15631565
});
1564-
assertEquals(OMException.ResultCodes.ETAG_MISMATCH, e.getResult());
1566+
assertEquals(ETAG_MISMATCH, e.getResult());
15651567
}
15661568

15671569
@ParameterizedTest

hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,8 @@ enum Status {
572572
ETAG_MISMATCH = 99;
573573

574574
ETAG_NOT_AVAILABLE = 100;
575+
576+
ATOMIC_WRITE_CONFLICT = 101;
575577
}
576578

577579
/**

hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -628,17 +628,18 @@ protected void validateAtomicRewrite(OmKeyInfo existing, OmKeyInfo toCommit, Map
628628

629629
if (expectedGen == OzoneConsts.EXPECTED_GEN_CREATE_IF_NOT_EXISTS) {
630630
if (existing != null) {
631-
throw new OMException("Key already exists",
632-
OMException.ResultCodes.KEY_ALREADY_EXISTS);
631+
throw new OMException("Atomic create-if-not-exists conflicted with an existing key",
632+
OMException.ResultCodes.ATOMIC_WRITE_CONFLICT);
633633
}
634634
} else {
635635
if (existing == null) {
636-
throw new OMException("Atomic rewrite is not allowed for a new key", KEY_NOT_FOUND);
636+
throw new OMException("Atomic rewrite conflicted because the key no longer exists",
637+
OMException.ResultCodes.ATOMIC_WRITE_CONFLICT);
637638
}
638639
if (expectedGen != existing.getUpdateID()) {
639640
throw new OMException("Cannot commit as current generation (" + existing.getUpdateID() +
640641
") does not match the expected generation to rewrite (" + expectedGen + ")",
641-
KEY_NOT_FOUND);
642+
OMException.ResultCodes.ATOMIC_WRITE_CONFLICT);
642643
}
643644
}
644645
}

hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,9 @@ protected OmKeyInfo createFileInfo(
10431043
.setUpdateID(transactionLogIndex)
10441044
.setOwnerName(keyArgs.getOwnerName())
10451045
.setFile(true);
1046+
if (keyArgs.hasExpectedDataGeneration()) {
1047+
builder.setExpectedDataGeneration(keyArgs.getExpectedDataGeneration());
1048+
}
10461049
if (omPathInfo instanceof OMFileRequest.OMPathInfoWithFSO) {
10471050
// FileTable metadata format
10481051
OMFileRequest.OMPathInfoWithFSO omPathInfoFSO

hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCommitRequest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.hadoop.ozone.om.request.key;
1919

20-
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.KEY_ALREADY_EXISTS;
20+
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.ATOMIC_WRITE_CONFLICT;
2121
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.KEY_NOT_FOUND;
2222
import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status.OK;
2323
import static org.assertj.core.api.Assertions.assertThat;
@@ -252,7 +252,7 @@ public void testAtomicRewrite() throws Exception {
252252
// However there is no closed key entry, so the commit should fail.
253253
OMClientResponse omClientResponse =
254254
omKeyCommitRequest.validateAndUpdateCache(ozoneManager, 100L);
255-
assertEquals(KEY_NOT_FOUND, omClientResponse.getOMResponse().getStatus());
255+
assertEquals(ATOMIC_WRITE_CONFLICT, omClientResponse.getOMResponse().getStatus());
256256

257257
// Now add the key to the key table, and try again, but with different generation
258258
omKeyInfoBuilder.setExpectedDataGeneration(null);
@@ -261,7 +261,7 @@ public void testAtomicRewrite() throws Exception {
261261
closedKeyTable.put(getOzonePathKey(), invalidKeyInfo);
262262
// This should fail as the updateID ia zero and the open key has rewrite generation of 1.
263263
omClientResponse = omKeyCommitRequest.validateAndUpdateCache(ozoneManager, 100L);
264-
assertEquals(KEY_NOT_FOUND, omClientResponse.getOMResponse().getStatus());
264+
assertEquals(ATOMIC_WRITE_CONFLICT, omClientResponse.getOMResponse().getStatus());
265265

266266
omKeyInfoBuilder.setUpdateID(1L);
267267
OmKeyInfo closedKeyInfo = omKeyInfoBuilder.build();
@@ -347,7 +347,7 @@ public void testAtomicCreateIfNotExistsCommitKeyAlreadyExists() throws Exception
347347

348348
OMClientResponse omClientResponse =
349349
omKeyCommitRequest.validateAndUpdateCache(ozoneManager, 100L);
350-
assertEquals(KEY_ALREADY_EXISTS, omClientResponse.getOMResponse().getStatus());
350+
assertEquals(ATOMIC_WRITE_CONFLICT, omClientResponse.getOMResponse().getStatus());
351351
}
352352

353353
@Test

hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ public void testCreateKeyExpectedGenCreateIfNotExistsKeyMissing(
166166
omKeyCreateRequest.validateAndUpdateCache(ozoneManager, 100L);
167167

168168
checkResponse(modifiedOmRequest, response, id, false, getBucketLayout());
169+
170+
OmKeyInfo openKeyInfo = omMetadataManager.getOpenKeyTable(getBucketLayout())
171+
.get(getOpenKey(id));
172+
assertNotNull(openKeyInfo);
173+
assertEquals(OzoneConsts.EXPECTED_GEN_CREATE_IF_NOT_EXISTS,
174+
openKeyInfo.getExpectedDataGeneration());
175+
assertNull(openKeyInfo.getExpectedETag());
169176
}
170177

171178
@ParameterizedTest

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ public Response put(
189189
throw newError(S3ErrorTable.NO_OVERWRITE, keyPath, ex);
190190
} else if (ex.getResult() == ResultCodes.KEY_ALREADY_EXISTS) {
191191
throw newError(PRECOND_FAILED, keyPath, ex);
192+
} else if (ex.getResult() == ResultCodes.ATOMIC_WRITE_CONFLICT) {
193+
throw newError(S3ErrorTable.CONDITIONAL_REQUEST_CONFLICT, keyPath, ex);
192194
} else if (ex.getResult() == ResultCodes.ETAG_MISMATCH) {
193195
throw newError(PRECOND_FAILED, keyPath, ex);
194196
} else if (ex.getResult() == ResultCodes.ETAG_NOT_AVAILABLE) {

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ public final class S3ErrorTable {
117117
public static final OS3Exception PRECOND_FAILED = new OS3Exception(
118118
"PreconditionFailed", "At least one of the pre-conditions you " +
119119
"specified did not hold", HTTP_PRECON_FAILED);
120+
121+
public static final OS3Exception CONDITIONAL_REQUEST_CONFLICT =
122+
new OS3Exception("ConditionalRequestConflict",
123+
"A conflicting conditional operation occurred. Retry the request.",
124+
HTTP_CONFLICT);
120125

121126
public static final OS3Exception NOT_IMPLEMENTED = new OS3Exception(
122127
"NotImplemented", "This part of feature is not implemented yet.",

0 commit comments

Comments
 (0)