This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 8cc6719 HDDS-5523. Fix multipart upload failure in s3 compatibility
tests (#2509)
8cc6719 is described below
commit 8cc671984acf943dc16585c759522fac49be9136
Author: GeorgeJahad <[email protected]>
AuthorDate: Thu Sep 30 07:15:05 2021 -0700
HDDS-5523. Fix multipart upload failure in s3 compatibility tests (#2509)
---
.../src/main/smoketest/s3/MultipartUpload.robot | 23 ++-
.../hadoop/ozone/s3/endpoint/ObjectEndpoint.java | 39 ++--
.../s3/endpoint/TestMultipartUploadWithCopy.java | 219 +++++++++++++++------
3 files changed, 210 insertions(+), 71 deletions(-)
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/MultipartUpload.robot
b/hadoop-ozone/dist/src/main/smoketest/s3/MultipartUpload.robot
index b9d99f2..6b5d7c1 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/MultipartUpload.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/MultipartUpload.robot
@@ -29,6 +29,12 @@ Create Random file
[arguments] ${size_in_megabytes}
Execute dd if=/dev/urandom of=/tmp/part1 bs=1048576
count=${size_in_megabytes}
+Wait Til Date Past
+ [arguments] ${date}
+ ${latestDate} = Get Current Date UTC
+ ${sleepSeconds} = Subtract Date From Date ${date} ${latestDate}
+ Run Keyword If ${sleepSeconds} > 0 Sleep ${sleepSeconds}
+
*** Variables ***
${ENDPOINT_URL} http://s3g:9878
${BUCKET} generated
@@ -279,7 +285,7 @@ Test Multipart Upload Put With Copy and range with
IfModifiedSince
Run Keyword Create Random file 10
${curDate} = Get Current Date
${beforeCreate} = Subtract Time From Date ${curDate} 1 day
- ${afterCreate} = Add Time To Date ${curDate} 1 day
+ ${tomorrow} = Add Time To Date ${curDate} 1 day
${result} = Execute AWSS3APICli put-object --bucket ${BUCKET}
--key ${PREFIX}/copyrange/source --body /tmp/part1
@@ -289,6 +295,14 @@ Test Multipart Upload Put With Copy and range with
IfModifiedSince
Should contain ${result} ${BUCKET}
Should contain ${result} UploadId
+#calc time-to-sleep from time-last-modified plus a few seconds
+ ${result} = Execute AWSS3APICli head-object --bucket
${BUCKET} --key ${PREFIX}/copyrange/source
+ ${lastModified} = Execute and checkrc echo '${result}' | jq -r
'.LastModified' 0
+ Should contain ${result} ${LastModified}
+ ${lmDate} = Convert Date ${lastModified}
date_format=%a, %d %b %Y %H:%M:%S %Z
+ ${afterCreate} = Add Time To Date ${lmDate} 3 seconds
+ Wait Til Date Past ${afterCreate}
+
${result} = Execute AWSS3APICli and checkrc upload-part-copy
--bucket ${BUCKET} --key ${PREFIX}/copyrange/destination --upload-id
${uploadID} --part-number 1 --copy-source ${BUCKET}/${PREFIX}/copyrange/source
--copy-source-range bytes=0-10485757 --copy-source-if-modified-since
'${afterCreate}' 255
Should contain ${result}
PreconditionFailed
@@ -307,6 +321,13 @@ Test Multipart Upload Put With Copy and range with
IfModifiedSince
${eTag2} = Execute and checkrc echo '${result}' | jq -r
'.CopyPartResult.ETag' 0
+# future date strings cause precondition to be ignored
+ ${result} = Execute AWSS3APICli and checkrc upload-part-copy
--bucket ${BUCKET} --key ${PREFIX}/copyrange/destination --upload-id
${uploadID} --part-number 1 --copy-source ${BUCKET}/${PREFIX}/copyrange/source
--copy-source-range bytes=0-10485757 --copy-source-if-modified-since
'${tomorrow}' 0
+ Should contain ${result} ETag
+ Should contain ${result} LastModified
+
+ ${eTag1} = Execute and checkrc echo '${result}' | jq -r
'.CopyPartResult.ETag' 0
+
Execute AWSS3APICli complete-multipart-upload
--upload-id ${uploadID} --bucket ${BUCKET} --key
${PREFIX}/copyrange/destination --multipart-upload
'Parts=[{ETag=${eTag1},PartNumber=1},{ETag=${eTag2},PartNumber=2}]'
Execute AWSS3APICli get-object --bucket ${BUCKET}
--key ${PREFIX}/copyrange/destination /tmp/part-result
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
index 03636c3..6d61426 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java
@@ -50,6 +50,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.OptionalLong;
import org.apache.hadoop.hdds.client.ReplicationFactor;
import org.apache.hadoop.hdds.client.ReplicationType;
@@ -858,33 +859,47 @@ public class ObjectEndpoint extends EndpointBase {
return partMarker;
}
- private static long parseOzoneDate(String ozoneDateStr) throws OS3Exception {
+ // Parses date string and return long representation. Returns an
+ // empty if DateStr is null or invalid. Dates in the future are
+ // considered invalid.
+ private static OptionalLong parseAndValidateDate(String ozoneDateStr) {
long ozoneDateInMs;
+ if (ozoneDateStr == null) {
+ return OptionalLong.empty();
+ }
try {
ozoneDateInMs = OzoneUtils.formatDate(ozoneDateStr);
} catch (ParseException e) {
- throw S3ErrorTable.newError(S3ErrorTable
- .INVALID_ARGUMENT, ozoneDateStr);
+ // if time not parseable, then return empty()
+ return OptionalLong.empty();
+ }
+
+ long currentDate = System.currentTimeMillis();
+ if (ozoneDateInMs <= currentDate){
+ return OptionalLong.of(ozoneDateInMs);
+ } else {
+ // dates in the future are invalid, so return empty()
+ return OptionalLong.empty();
}
- return ozoneDateInMs;
}
private boolean checkCopySourceModificationTime(Long lastModificationTime,
String copySourceIfModifiedSinceStr,
- String copySourceIfUnmodifiedSinceStr) throws OS3Exception {
+ String copySourceIfUnmodifiedSinceStr) {
long copySourceIfModifiedSince = Long.MIN_VALUE;
long copySourceIfUnmodifiedSince = Long.MAX_VALUE;
- if (copySourceIfModifiedSinceStr != null) {
- copySourceIfModifiedSince =
- parseOzoneDate(copySourceIfModifiedSinceStr);
+ OptionalLong modifiedDate =
+ parseAndValidateDate(copySourceIfModifiedSinceStr);
+ if (modifiedDate.isPresent()) {
+ copySourceIfModifiedSince = modifiedDate.getAsLong();
}
- if (copySourceIfUnmodifiedSinceStr != null) {
- copySourceIfUnmodifiedSince =
- parseOzoneDate(copySourceIfUnmodifiedSinceStr);
+ OptionalLong unmodifiedDate =
+ parseAndValidateDate(copySourceIfUnmodifiedSinceStr);
+ if (unmodifiedDate.isPresent()) {
+ copySourceIfUnmodifiedSince = unmodifiedDate.getAsLong();
}
-
return (copySourceIfModifiedSince <= lastModificationTime) &&
(lastModificationTime <= copySourceIfUnmodifiedSince);
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
index 29f6dcf..f015b63 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestMultipartUploadWithCopy.java
@@ -72,9 +72,14 @@ public class TestMultipartUploadWithCopy {
private static final String EXISTING_KEY = "key1";
private static final String EXISTING_KEY_CONTENT = "testkey";
private static final OzoneClient CLIENT = new OzoneClientStub();
- private static final int RANGE_FROM = 2;
- private static final int RANGE_TO = 4;
-
+ private static final long DELAY_MS = 2000;
+ private static long sourceKeyLastModificationTime;
+ private static String beforeSourceKeyModificationTimeStr;
+ private static String afterSourceKeyModificationTimeStr;
+ private static String futureTimeStr;
+ private static final String UNPARSABLE_TIME_STR = "Unparsable time string";
+ private static final String ERROR_CODE =
+ S3ErrorTable.PRECOND_FAILED.getCode();
@BeforeClass
public static void setUp() throws Exception {
CLIENT.getObjectStore().createS3Bucket(OzoneConsts.S3_BUCKET);
@@ -89,6 +94,26 @@ public class TestMultipartUploadWithCopy {
stream.write(keyContent);
}
+ sourceKeyLastModificationTime = CLIENT.getObjectStore()
+ .getS3Bucket(OzoneConsts.S3_BUCKET)
+ .getKey(EXISTING_KEY)
+ .getModificationTime().toEpochMilli();
+ beforeSourceKeyModificationTimeStr =
+ OzoneUtils.formatTime(sourceKeyLastModificationTime - 1000);
+ afterSourceKeyModificationTimeStr =
+ OzoneUtils.formatTime(sourceKeyLastModificationTime + DELAY_MS);
+ futureTimeStr =
+ OzoneUtils.formatTime(sourceKeyLastModificationTime +
+ 1000 * 60 * 24);
+
+ // Make sure DELAY_MS has passed, otherwise
+ // afterSourceKeyModificationTimeStr will be in the future
+ // and thus invalid
+ long currentTime = System.currentTimeMillis();
+ long sleepMs = sourceKeyLastModificationTime + DELAY_MS - currentTime;
+ if (sleepMs > 0) {
+ Thread.sleep(sleepMs);
+ }
HttpHeaders headers = Mockito.mock(HttpHeaders.class);
when(headers.getHeaderString(STORAGE_CLASS_HEADER)).thenReturn(
"STANDARD");
@@ -99,15 +124,6 @@ public class TestMultipartUploadWithCopy {
@Test
public void testMultipart() throws Exception {
- Long sourceKeyLastModificationTime = CLIENT.getObjectStore()
- .getS3Bucket(OzoneConsts.S3_BUCKET)
- .getKey(EXISTING_KEY)
- .getModificationTime().toEpochMilli();
- String beforeSourceKeyModificationTimeStr =
- OzoneUtils.formatTime(sourceKeyLastModificationTime - 1000);
- String afterSourceKeyModificationTimeStr =
- OzoneUtils.formatTime(sourceKeyLastModificationTime + 1000);
-
// Initiate multipart upload
String uploadID = initiateMultipartUpload(KEY);
@@ -156,55 +172,132 @@ public class TestMultipartUploadWithCopy {
}
}
- @Test
- public void testMultipartIfModifiedSince() throws Exception {
- Long sourceKeyLastModificationTime = CLIENT.getObjectStore()
- .getS3Bucket(OzoneConsts.S3_BUCKET)
- .getKey(EXISTING_KEY)
- .getModificationTime().toEpochMilli();
- String beforeSourceKeyModificationTimeStr =
- OzoneUtils.formatTime(sourceKeyLastModificationTime - 1000);
- String afterSourceKeyModificationTimeStr =
- OzoneUtils.formatTime(sourceKeyLastModificationTime + 1000);
-
- // Initiate multipart upload
- String uploadID = initiateMultipartUpload(KEY);
-
- // ifUnmodifiedSince = beforeSourceKeyModificationTime,
- // ifModifiedSince = afterSourceKeyModificationTime
- try {
- uploadPartWithCopy(KEY, uploadID, 1,
- OzoneConsts.S3_BUCKET + "/" + EXISTING_KEY, "bytes=0-3",
- afterSourceKeyModificationTimeStr,
- beforeSourceKeyModificationTimeStr
- );
- fail("testMultipartIfModifiedSinceError");
- } catch (OS3Exception ex) {
- assertEquals(ex.getCode(), S3ErrorTable.PRECOND_FAILED.getCode());
+ /**
+ * CopyIfTimestampTestCase captures all the possibilities for the time stamps
+ * that can be passed into the multipart copy with copy-if flags for
+ * timestamps. Only some of the cases are valid others should raise an
+ * exception.
+ * Time stamps can be,
+ * 1. after the timestamp on the object but still a valid time stamp
+ * (in regard to wall clock time on server)
+ * 2. before the timestamp on the object
+ * 3. In the Future beyond the wall clock time on the server
+ * 4. Null
+ * 5. Unparsable
+ */
+ public enum CopyIfTimestampTestCase {
+ MODIFIED_SINCE_AFTER_TS_UNMODIFIED_SINCE_AFTER_TS(
+ afterSourceKeyModificationTimeStr, afterSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_AFTER_TS_UNMODIFIED_SINCE_BEFORE_TS(
+ afterSourceKeyModificationTimeStr, beforeSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_AFTER_TS_UNMODIFIED_SINCE_NULL(
+ afterSourceKeyModificationTimeStr, null,
+ ERROR_CODE),
+ MODIFIED_SINCE_AFTER_TS_UNMODIFIED_SINCE_FUTURE(
+ afterSourceKeyModificationTimeStr, futureTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_AFTER_TS_UNMODIFIED_SINCE_UNPARSABLE_TS(
+ afterSourceKeyModificationTimeStr, UNPARSABLE_TIME_STR,
+ ERROR_CODE),
+
+ MODIFIED_SINCE_BEFORE_TS_UNMODIFIED_SINCE_AFTER_TS(
+ beforeSourceKeyModificationTimeStr, afterSourceKeyModificationTimeStr,
+ null),
+ MODIFIED_SINCE_BEFORE_TS_UNMODIFIED_SINCE_BEFORE_TS(
+ beforeSourceKeyModificationTimeStr, beforeSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_BEFORE_TS_UNMODIFIED_SINCE_NULL(
+ beforeSourceKeyModificationTimeStr, null,
+ null),
+ MODIFIED_SINCE_BEFORE_TS_UNMOFIFIED_SINCE_FUTURE(
+ beforeSourceKeyModificationTimeStr, futureTimeStr,
+ null),
+ MODIFIED_SINCE_BEFORE_TS_UNMODIFIED_SINCE_UNPARSABLE_TS(
+ beforeSourceKeyModificationTimeStr, UNPARSABLE_TIME_STR,
+ null),
+
+ MODIFIED_SINCE_NULL_TS_UNMODIFIED_SINCE_AFTER_TS(
+ null, afterSourceKeyModificationTimeStr,
+ null),
+ MODIFIED_SINCE_NULL_TS_UNMODIFIED_SINCE_BEFORE_TS(
+ null, beforeSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_NULL_TS_UNMODIFIED_SINCE_NULL_TS(
+ null, null,
+ null),
+ MODIFIED_SINCE_NULL_TS_UNMODIFIED_SINCE_FUTURE_TS(
+ null, futureTimeStr,
+ null),
+ MODIFIED_SINCE_NULL_TS_UNMODIFIED_SINCE_UNPARSABLE_TS(
+ null, UNPARSABLE_TIME_STR,
+ null),
+
+ MODIFIED_SINCE_UNPARSABLE_TS_UNMODIFIED_SINCE_AFTER_TS(
+ UNPARSABLE_TIME_STR, afterSourceKeyModificationTimeStr,
+ null),
+ MODIFIED_SINCE_UNPARSABLE_TS_UNMODIFIED_SINCE_BEFORE_TS(
+ UNPARSABLE_TIME_STR, beforeSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_UNPARSABLE_TS_UNMODIFIED_SINCE_NULL_TS(
+ UNPARSABLE_TIME_STR, null,
+ null),
+ MODIFIED_SINCE_UNPARSABLE_TS_UNMODIFIED_SINCE_FUTURE_TS(
+ UNPARSABLE_TIME_STR, futureTimeStr,
+ null),
+ MODIFIED_SINCE_UNPARSABLE_TS_UNMODIFIED_SINCE_UNPARSABLE_TS(
+ UNPARSABLE_TIME_STR, UNPARSABLE_TIME_STR,
+ null),
+
+ MODIFIED_SINCE_FUTURE_TS_UNMODIFIED_SINCE_AFTER_TS(
+ futureTimeStr, afterSourceKeyModificationTimeStr,
+ null),
+ MODIFIED_SINCE_FUTURE_TS_UNMODIFIED_SINCE_BEFORE_TS(
+ futureTimeStr, beforeSourceKeyModificationTimeStr,
+ ERROR_CODE),
+ MODIFIED_SINCE_FUTURE_TS_UNMODIFIED_SINCE_NULL_TS(
+ futureTimeStr, null,
+ null),
+ MODIFIED_SINCE_FUTURE_TS_UNMODIFIED_SINCE_FUTURE_TS(
+ futureTimeStr, futureTimeStr,
+ null),
+ MODIFIED_SINCE_FUTURE_TS_UNMODIFIED_SINCE_UNPARSABLE_TS(
+ futureTimeStr, UNPARSABLE_TIME_STR,
+ null);
+
+ private final String modifiedTimestamp;
+ private final String unmodifiedTimestamp;
+ private final String errorCode;
+
+ CopyIfTimestampTestCase(String modifiedTimestamp,
+ String unmodifiedTimestamp, String errorCode) {
+ this.modifiedTimestamp = modifiedTimestamp;
+ this.unmodifiedTimestamp = unmodifiedTimestamp;
+ this.errorCode = errorCode;
}
- // ifUnmodifiedSince = beforeSourceKeyModificationTime,
- try {
- uploadPartWithCopy(KEY, uploadID, 1,
- OzoneConsts.S3_BUCKET + "/" + EXISTING_KEY, "bytes=0-3",
- null,
- beforeSourceKeyModificationTimeStr
- );
- fail("testMultipartIfModifiedSinceError");
- } catch (OS3Exception ex) {
- assertEquals(ex.getCode(), S3ErrorTable.PRECOND_FAILED.getCode());
+ @Override
+ public String toString() {
+ return this.name() +
+ " Modified:" + this.modifiedTimestamp
+ + " Unmodified:" + this.unmodifiedTimestamp
+ + " ErrorCode:" + this.errorCode;
}
-
- // ifModifiedSince = afterSourceKeyModificationTime
- try {
- uploadPartWithCopy(KEY, uploadID, 1,
- OzoneConsts.S3_BUCKET + "/" + EXISTING_KEY, "bytes=0-3",
- afterSourceKeyModificationTimeStr,
- null
- );
- fail("testMultipartIfModifiedSinceError");
- } catch (OS3Exception ex) {
- assertEquals(ex.getCode(), S3ErrorTable.PRECOND_FAILED.getCode());
+ }
+ @Test
+ public void testMultipartTSHeaders() throws Exception {
+ for (CopyIfTimestampTestCase t : CopyIfTimestampTestCase.values()) {
+ try {
+ uploadPartWithCopy(t.modifiedTimestamp, t.unmodifiedTimestamp);
+ if (t.errorCode != null) {
+ fail("Fail test:" + t);
+ }
+ } catch (OS3Exception ex) {
+ if ((t.errorCode == null) || (!ex.getCode().equals(ERROR_CODE))) {
+ fail("Failed test:" + t);
+ }
+ }
}
}
@@ -246,6 +339,16 @@ public class TestMultipartUploadWithCopy {
range, null, null);
}
+ private Part uploadPartWithCopy(String ifModifiedSinceStr,
+ String ifUnmodifiedSinceStr) throws IOException, OS3Exception {
+ // Initiate multipart upload
+ String uploadID = initiateMultipartUpload(KEY);
+
+ return uploadPartWithCopy(KEY, uploadID, 1,
+ OzoneConsts.S3_BUCKET + "/" + EXISTING_KEY, "bytes=0-3",
+ ifModifiedSinceStr, ifUnmodifiedSinceStr);
+ }
+
private Part uploadPartWithCopy(String key, String uploadID, int partNumber,
String keyOrigin, String range, String ifModifiedSinceStr,
String ifUnmodifiedSinceStr) throws IOException, OS3Exception {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]