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]

Reply via email to