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 babf85c762 HDDS-10403. CopyObject should set ETag based on the key 
content (#6251)
babf85c762 is described below

commit babf85c762ec159b6086145299f9a69b0a27f2ae
Author: Ivan Andika <[email protected]>
AuthorDate: Fri Feb 23 20:35:43 2024 +0800

    HDDS-10403. CopyObject should set ETag based on the key content (#6251)
---
 .../dist/src/main/smoketest/s3/objectcopy.robot    | 14 +++++++++++
 .../hadoop/ozone/s3/endpoint/ObjectEndpoint.java   | 17 ++++++++------
 .../ozone/s3/endpoint/ObjectEndpointStreaming.java |  9 +++++---
 .../hadoop/ozone/client/OzoneBucketStub.java       |  2 +-
 .../hadoop/ozone/s3/endpoint/TestObjectPut.java    | 27 ++++++++++++++++++++++
 5 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
index 21764d65c4..af7571d35b 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/objectcopy.robot
@@ -37,15 +37,26 @@ Create Dest Bucket
 Copy Object Happy Scenario
     Run Keyword if    '${DESTBUCKET}' == 'generated1'    Create Dest Bucket
                         Execute                    date > /tmp/copyfile
+    ${file_checksum} =  Execute                    md5sum /tmp/copyfile | awk 
'{print $1}'
+
     ${result} =         Execute AWSS3ApiCli        put-object --bucket 
${BUCKET} --key ${PREFIX}/copyobject/key=value/f1 --body /tmp/copyfile
+    ${eTag} =           Execute and checkrc        echo '${result}' | jq -r 
'.ETag'  0
+                        Should Be Equal            ${eTag}           
\"${file_checksum}\"
+
     ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${BUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
 
     ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/key=value/f1
+    ${eTag} =           Execute and checkrc        echo '${result}' | jq -r 
'.CopyObjectResult.ETag'  0
+                        Should Be Equal            ${eTag}           
\"${file_checksum}\"
+
     ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
     #copying again will not throw error
     ${result} =         Execute AWSS3ApiCli        copy-object --bucket 
${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/${PREFIX}/copyobject/key=value/f1
+    ${eTag} =           Execute and checkrc        echo '${result}' | jq -r 
'.CopyObjectResult.ETag'  0
+                        Should Be Equal            ${eTag}           
\"${file_checksum}\"
+
     ${result} =         Execute AWSS3ApiCli        list-objects --bucket 
${DESTBUCKET} --prefix ${PREFIX}/copyobject/key=value/
                         Should contain             ${result}         f1
 
@@ -56,8 +67,11 @@ Copy Object Where Bucket is not available
                         Should contain             ${result}        
NoSuchBucket
 
 Copy Object Where both source and dest are same with change to storageclass
+     ${file_checksum} =  Execute                    md5sum /tmp/copyfile | awk 
'{print $1}'
      ${result} =         Execute AWSS3APICli        copy-object 
--storage-class REDUCED_REDUNDANCY --bucket ${DESTBUCKET} --key 
${PREFIX}/copyobject/key=value/f1 --copy-source 
${DESTBUCKET}/${PREFIX}/copyobject/key=value/f1
                          Should contain             ${result}        ETag
+     ${eTag} =           Execute and checkrc        echo '${result}' | jq -r 
'.CopyObjectResult.ETag'  0
+                         Should Be Equal            ${eTag}           
\"${file_checksum}\"
 
 Copy Object Where Key not available
     ${result} =         Execute AWSS3APICli and checkrc        copy-object 
--bucket ${DESTBUCKET} --key ${PREFIX}/copyobject/key=value/f1 --copy-source 
${BUCKET}/nonnonexistentkey       255
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 24115abe8e..0514125abd 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
@@ -1118,13 +1118,14 @@ public class ObjectEndpoint extends EndpointBase {
       PerformanceStringBuilder perf, long startNanos)
       throws IOException {
     long copyLength;
+    src = new DigestInputStream(src, E_TAG_PROVIDER.get());
     if (datastreamEnabled && !(replication != null &&
         replication.getReplicationType() == EC) &&
         srcKeyLen > datastreamMinLength) {
       perf.appendStreamMode();
       copyLength = ObjectEndpointStreaming
           .copyKeyWithStream(volume.getBucket(destBucket), destKey, srcKeyLen,
-              chunkSize, replication, metadata, src, perf, startNanos);
+              chunkSize, replication, metadata, (DigestInputStream) src, perf, 
startNanos);
     } else {
       try (OzoneOutputStream dest = getClientProtocol()
           .createKey(volume.getName(), destBucket, destKey, srcKeyLen,
@@ -1133,6 +1134,10 @@ public class ObjectEndpoint extends EndpointBase {
             getMetrics().updateCopyKeyMetadataStats(startNanos);
         perf.appendMetaLatencyNanos(metadataLatencyNs);
         copyLength = IOUtils.copyLarge(src, dest);
+        String eTag = DatatypeConverter.printHexBinary(
+                ((DigestInputStream) src).getMessageDigest().digest())
+            .toLowerCase();
+        dest.getMetadata().put(ETAG, eTag);
       }
     }
     getMetrics().incCopyObjectSuccessLength(copyLength);
@@ -1151,8 +1156,9 @@ public class ObjectEndpoint extends EndpointBase {
     String sourceBucket = result.getLeft();
     String sourceKey = result.getRight();
     try {
+      OzoneKeyDetails sourceKeyDetails = getClientProtocol().getKeyDetails(
+          volume.getName(), sourceBucket, sourceKey);
       // Checking whether we trying to copying to it self.
-
       if (sourceBucket.equals(destBucket) && sourceKey
           .equals(destkey)) {
         // When copying to same storage type when storage type is provided,
@@ -1171,15 +1177,12 @@ public class ObjectEndpoint extends EndpointBase {
           // still does not support this just returning dummy response
           // for now
           CopyObjectResponse copyObjectResponse = new CopyObjectResponse();
-          copyObjectResponse.setETag(OzoneUtils.getRequestID());
+          
copyObjectResponse.setETag(wrapInQuotes(sourceKeyDetails.getMetadata().get(ETAG)));
           copyObjectResponse.setLastModified(Instant.ofEpochMilli(
               Time.now()));
           return copyObjectResponse;
         }
       }
-
-      OzoneKeyDetails sourceKeyDetails = getClientProtocol().getKeyDetails(
-          volume.getName(), sourceBucket, sourceKey);
       long sourceKeyLen = sourceKeyDetails.getDataSize();
 
       try (OzoneInputStream src = getClientProtocol().getKey(volume.getName(),
@@ -1194,7 +1197,7 @@ public class ObjectEndpoint extends EndpointBase {
 
       getMetrics().updateCopyObjectSuccessStats(startNanos);
       CopyObjectResponse copyObjectResponse = new CopyObjectResponse();
-      copyObjectResponse.setETag(OzoneUtils.getRequestID());
+      
copyObjectResponse.setETag(wrapInQuotes(destKeyDetails.getMetadata().get(ETAG)));
       copyObjectResponse.setLastModified(destKeyDetails.getModificationTime());
       return copyObjectResponse;
     } catch (OMException ex) {
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java
index bbb743ee35..b916fc111d 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java
@@ -122,15 +122,18 @@ final class ObjectEndpointStreaming {
       int bufferSize,
       ReplicationConfig replicationConfig,
       Map<String, String> keyMetadata,
-      InputStream body, PerformanceStringBuilder perf, long startNanos)
+      DigestInputStream body, PerformanceStringBuilder perf, long startNanos)
       throws IOException {
-    long writeLen = 0;
+    long writeLen;
     try (OzoneDataStreamOutput streamOutput = bucket.createStreamKey(keyPath,
         length, replicationConfig, keyMetadata)) {
       long metadataLatencyNs =
           METRICS.updateCopyKeyMetadataStats(startNanos);
-      perf.appendMetaLatencyNanos(metadataLatencyNs);
       writeLen = writeToStreamOutput(streamOutput, body, bufferSize, length);
+      String eTag = 
DatatypeConverter.printHexBinary(body.getMessageDigest().digest())
+          .toLowerCase();
+      perf.appendMetaLatencyNanos(metadataLatencyNs);
+      ((KeyMetadataAware)streamOutput).getMetadata().put(OzoneConsts.ETAG, 
eTag);
     }
     return writeLen;
   }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java
index 39ae9cc4af..0cbe0781c4 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneBucketStub.java
@@ -209,7 +209,7 @@ public class OzoneBucketStub extends OzoneBucket {
                                                Map<String, String> keyMetadata)
       throws IOException {
     ByteBufferStreamOutput byteBufferStreamOutput =
-        new ByteBufferStreamOutput() {
+        new KeyMetadataAwareByteBufferStreamOutput(keyMetadata) {
 
           private final ByteBuffer buffer = ByteBuffer.allocate((int) size);
 
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
index ae8279f258..0daa666ae4 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
@@ -26,9 +26,11 @@ import java.io.InputStream;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.client.ECReplicationConfig;
 import org.apache.hadoop.hdds.client.ReplicationType;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneClient;
@@ -52,7 +54,9 @@ import static 
org.apache.hadoop.ozone.s3.util.S3Consts.STORAGE_CLASS_HEADER;
 import static org.apache.hadoop.ozone.s3.util.S3Utils.urlEncode;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
@@ -108,9 +112,12 @@ public class TestObjectPut {
             .readKey(keyName);
     String keyContent =
         IOUtils.toString(ozoneInputStream, UTF_8);
+    OzoneKeyDetails keyDetails = 
clientStub.getObjectStore().getS3Bucket(bucketName).getKey(keyName);
 
     assertEquals(200, response.getStatus());
     assertEquals(CONTENT, keyContent);
+    assertNotNull(keyDetails.getMetadata());
+    
assertTrue(StringUtils.isNotEmpty(keyDetails.getMetadata().get(OzoneConsts.ETAG)));
   }
 
   @Test
@@ -136,9 +143,12 @@ public class TestObjectPut {
             .readKey(keyName);
     String keyContent =
         IOUtils.toString(ozoneInputStream, UTF_8);
+    OzoneKeyDetails keyDetails = 
clientStub.getObjectStore().getS3Bucket(bucketName).getKey(keyName);
 
     assertEquals(200, response.getStatus());
     assertEquals(CONTENT, keyContent);
+    assertNotNull(keyDetails.getMetadata());
+    
assertTrue(StringUtils.isNotEmpty(keyDetails.getMetadata().get(OzoneConsts.ETAG)));
   }
 
   @Test
@@ -208,9 +218,12 @@ public class TestObjectPut {
         clientStub.getObjectStore().getS3Bucket(bucketName)
             .readKey(keyName);
     String keyContent = IOUtils.toString(ozoneInputStream, UTF_8);
+    OzoneKeyDetails keyDetails = 
clientStub.getObjectStore().getS3Bucket(bucketName).getKey(keyName);
 
     assertEquals(200, response.getStatus());
     assertEquals("1234567890abcde", keyContent);
+    assertNotNull(keyDetails.getMetadata());
+    
assertTrue(StringUtils.isNotEmpty(keyDetails.getMetadata().get(OzoneConsts.ETAG)));
   }
 
   @Test
@@ -230,10 +243,14 @@ public class TestObjectPut {
         .readKey(keyName);
 
     String keyContent = IOUtils.toString(ozoneInputStream, UTF_8);
+    OzoneKeyDetails keyDetails = 
clientStub.getObjectStore().getS3Bucket(bucketName).getKey(keyName);
 
     assertEquals(200, response.getStatus());
     assertEquals(CONTENT, keyContent);
+    assertNotNull(keyDetails.getMetadata());
+    
assertTrue(StringUtils.isNotEmpty(keyDetails.getMetadata().get(OzoneConsts.ETAG)));
 
+    String sourceETag = keyDetails.getMetadata().get(OzoneConsts.ETAG);
 
     // Add copy header, and then call put
     when(headers.getHeaderString(COPY_SOURCE_HEADER)).thenReturn(
@@ -247,9 +264,19 @@ public class TestObjectPut {
         .readKey(destkey);
 
     keyContent = IOUtils.toString(ozoneInputStream, UTF_8);
+    OzoneKeyDetails sourceKeyDetails = clientStub.getObjectStore()
+        .getS3Bucket(bucketName).getKey(keyName);
+    OzoneKeyDetails destKeyDetails = clientStub.getObjectStore()
+        .getS3Bucket(destBucket).getKey(destkey);
 
     assertEquals(200, response.getStatus());
     assertEquals(CONTENT, keyContent);
+    assertNotNull(keyDetails.getMetadata());
+    
assertTrue(StringUtils.isNotEmpty(keyDetails.getMetadata().get(OzoneConsts.ETAG)));
+    // Source key eTag should remain unchanged and the dest key should have
+    // the same Etag since the key content is the same
+    assertEquals(sourceETag, 
sourceKeyDetails.getMetadata().get(OzoneConsts.ETAG));
+    assertEquals(sourceETag, 
destKeyDetails.getMetadata().get(OzoneConsts.ETAG));
 
     // source and dest same
     OS3Exception e = assertThrows(OS3Exception.class, () -> objectEndpoint.put(


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to