Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ public long getClientID() {
return clientID;
}

@VisibleForTesting
public KeyDataStreamOutput() {
super(null);
this.config = new OzoneClientConfig();
OmKeyInfo info = new OmKeyInfo.Builder().setKeyName("test").build();
blockDataStreamOutputEntryPool =
new BlockDataStreamOutputEntryPool(
config,
null,
null,
null, 0,
false, info,
false,
null,
0L);

this.writeOffset = 0;
this.clientID = 0L;
this.atomicKeyCreation = false;
}

@SuppressWarnings({"parameternumber", "squid:S00107"})
public KeyDataStreamOutput(
OzoneClientConfig config,
Expand Down
52 changes: 52 additions & 0 deletions hadoop-ozone/dist/src/main/smoketest/s3/MultipartUpload.robot
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,55 @@ Check Bucket Ownership Verification
${uploadID}= Execute and checkrc echo '${uploadID}' | jq -r '.UploadId' 0

Execute AWSS3APICli with bucket owner check abort-multipart-upload --bucket ${BUCKET} --key ${PREFIX}/mpu/aborttest --upload-id ${uploadID} ${correct_owner}

Test Multipart Upload Part with Content-MD5 header
# Create test file for multipart upload
Execute echo "Multipart Upload Part Test" > /tmp/mpu_md5testfile
${md5_hash} = Execute md5sum /tmp/mpu_md5testfile | awk '{print $1}'
${md5_base64} = Execute openssl dgst -md5 -binary /tmp/mpu_md5testfile | base64

# Initialize multipart upload
${uploadID} = Initiate MPU ${BUCKET} ${PREFIX}/mpu/md5test/key1

# Upload part with correct Content-MD5 header
${result} = Execute AWSS3APICli upload-part --bucket ${BUCKET} --key ${PREFIX}/mpu/md5test/key1 --part-number 1 --body /tmp/mpu_md5testfile --upload-id ${uploadID} --content-md5 ${md5_base64}
Should contain ${result} ETag
${eTag1} = Execute and checkrc echo '${result}' | jq -r '.ETag' | tr -d '"' 0
Should Be Equal ${eTag1} ${md5_hash}

# List parts to verify upload
${result} = Execute AWSS3APICli list-parts --bucket ${BUCKET} --key ${PREFIX}/mpu/md5test/key1 --upload-id ${uploadID}
${part_etag} = Execute and checkrc echo '${result}' | jq -r '.Parts[0].ETag' | tr -d '"' 0
Should Be Equal ${part_etag} ${md5_hash}

# Complete the multipart upload
${parts} = Set Variable {ETag=\"${eTag1}\",PartNumber=1}
Complete MPU ${BUCKET} ${PREFIX}/mpu/md5test/key1 ${uploadID} ${parts}

# Verify the final object
${result} = Execute AWSS3APICli get-object --bucket ${BUCKET} --key ${PREFIX}/mpu/md5test/key1 /tmp/mpu_md5testfile.result
Compare files /tmp/mpu_md5testfile /tmp/mpu_md5testfile.result

Test Multipart Upload Part with wrong Content-MD5 header
# Create test file for multipart upload
Execute echo "Multipart Upload Part Wrong MD5 Test" > /tmp/mpu_md5testfile2

# Calculate wrong MD5 (from different content)
${wrong_md5_hash} = Execute echo -n "wrong content for mpu" | md5sum | awk '{print $1}'
${wrong_md5_base64} = Execute echo -n "wrong content for mpu" | openssl dgst -md5 -binary | base64

# Initialize multipart upload
${uploadID} = Initiate MPU ${BUCKET} ${PREFIX}/mpu/md5test/key2

# Upload part with wrong Content-MD5 header - should fail
${result} = Execute AWSS3APICli and checkrc upload-part --bucket ${BUCKET} --key ${PREFIX}/mpu/md5test/key2 --part-number 1 --body /tmp/mpu_md5testfile2 --upload-id ${uploadID} --content-md5 ${wrong_md5_base64} 255
Should contain ${result} BadDigest

# Verify no parts were uploaded
${result} = Execute AWSS3APICli list-parts --bucket ${BUCKET} --key ${PREFIX}/mpu/md5test/key2 --upload-id ${uploadID}
${parts_count} = Execute and checkrc echo '${result}' | jq -r '.Parts | length' 0
Should Be Equal ${parts_count} 0

# Abort the multipart upload (cleanup)
Abort MPU ${BUCKET} ${PREFIX}/mpu/md5test/key2 ${uploadID}

23 changes: 23 additions & 0 deletions hadoop-ozone/dist/src/main/smoketest/s3/objectputget.robot
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,26 @@ Check Bucket Ownership Verification
# create directory
Execute touch /tmp/emptyfile
Execute AWSS3APICli with bucket owner check put-object --bucket ${BUCKET} --key ${PREFIX}/bucketownercondition/key=value/dir/ --body /tmp/emptyfile ${correct_owner}

Put object with Content-MD5 header
Execute echo "bar" > /tmp/md5testfile
${md5_hash} = Execute md5sum /tmp/md5testfile | awk '{print $1}'
${md5_base64} = Execute openssl dgst -md5 -binary /tmp/md5testfile | base64
${result} = Execute AWSS3APICli put-object --bucket ${BUCKET} --key ${PREFIX}/putobject/md5test/key1 --body /tmp/md5testfile --content-md5 ${md5_base64}
Should contain ${result} ETag
${etag} = Execute and checkrc echo '${result}' | jq -r '.ETag' | tr -d '"' 0
Should Be Equal ${etag} ${md5_hash}
${result} = Execute AWSS3APICli get-object --bucket ${BUCKET} --key ${PREFIX}/putobject/md5test/key1 /tmp/md5testfile.result
${etag} = Execute and checkrc echo '${result}' | jq -r '.ETag' | tr -d '"' 0
Should Be Equal ${etag} ${md5_hash}
Compare files /tmp/md5testfile /tmp/md5testfile.result

Put object with wrong Content-MD5 header
Execute echo "bar" > /tmp/md5testfile2
${wrong_md5_base64} = Execute echo -n "wrong" | openssl dgst -md5 -binary | base64
${result} = Execute AWSS3APICli and checkrc put-object --bucket ${BUCKET} --key ${PREFIX}/putobject/md5test/key2 --body /tmp/md5testfile2 --content-md5 ${wrong_md5_base64} 255
Should contain ${result} BadDigest
# Verify the object was not uploaded
${result} = Execute AWSS3APICli and checkrc head-object --bucket ${BUCKET} --key ${PREFIX}/putobject/md5test/key2 255
Should contain ${result} 404

Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -376,6 +377,163 @@ public void testPutObject() {
assertEquals("37b51d194a7513e45b56f6524f2d51f2", putObjectResult.getETag());
}

@Test
public void testPutObjectWithMD5Header() throws Exception {
final String bucketName = getBucketName();
final String keyName = getKeyName();
final String content = "bar";
s3Client.createBucket(bucketName);

byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
byte[] md5Bytes = calculateDigest(new ByteArrayInputStream(contentBytes), 0, contentBytes.length);
String md5Base64 = Base64.getEncoder().encodeToString(md5Bytes);

InputStream is = new ByteArrayInputStream(contentBytes);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentMD5(md5Base64);
objectMetadata.setContentLength(contentBytes.length);

PutObjectResult putObjectResult = s3Client.putObject(bucketName, keyName, is, objectMetadata);
assertEquals("37b51d194a7513e45b56f6524f2d51f2", putObjectResult.getETag());

S3Object object = s3Client.getObject(bucketName, keyName);
assertEquals(content.length(), object.getObjectMetadata().getContentLength());
assertEquals("37b51d194a7513e45b56f6524f2d51f2", object.getObjectMetadata().getETag());
}

@Test
public void testPutObjectWithWrongMD5Header() throws Exception {
final String bucketName = getBucketName();
final String keyName = getKeyName();
final String content = "bar";
s3Client.createBucket(bucketName);

// Use wrong content to calculate MD5
byte[] wrongContentBytes = "wrong".getBytes(StandardCharsets.UTF_8);
byte[] wrongMd5Bytes = calculateDigest(new ByteArrayInputStream(wrongContentBytes), 0, wrongContentBytes.length);
String wrongMd5Base64 = Base64.getEncoder().encodeToString(wrongMd5Bytes);

byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
InputStream is = new ByteArrayInputStream(contentBytes);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentMD5(wrongMd5Base64);
objectMetadata.setContentLength(contentBytes.length);

AmazonServiceException ase = assertThrows(AmazonServiceException.class,
() -> s3Client.putObject(bucketName, keyName, is, objectMetadata));

assertEquals(ErrorType.Client, ase.getErrorType());
assertEquals(400, ase.getStatusCode());
assertEquals("BadDigest", ase.getErrorCode());

// Verify the object was not uploaded
assertFalse(s3Client.doesObjectExist(bucketName, keyName));
}

@Test
public void testMultipartUploadWithMD5Header() throws Exception {
final String bucketName = getBucketName();
final String keyName = getKeyName();
s3Client.createBucket(bucketName);

// Initiate multipart upload
InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, keyName);
InitiateMultipartUploadResult initResponse = s3Client.initiateMultipartUpload(initRequest);
String uploadId = initResponse.getUploadId();

// Prepare part data
String part1Content = "part1data";
byte[] part1Bytes = part1Content.getBytes(StandardCharsets.UTF_8);
byte[] part1Md5Bytes = calculateDigest(new ByteArrayInputStream(part1Bytes), 0, part1Bytes.length);
String part1Md5Base64 = Base64.getEncoder().encodeToString(part1Md5Bytes);

// Upload part 1 with MD5
InputStream part1InputStream = new ByteArrayInputStream(part1Bytes);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentMD5(part1Md5Base64);
metadata.setContentLength(part1Bytes.length);

UploadPartRequest uploadRequest = new UploadPartRequest()
.withBucketName(bucketName)
.withKey(keyName)
.withUploadId(uploadId)
.withPartNumber(1)
.withInputStream(part1InputStream)
.withPartSize(part1Bytes.length)
.withObjectMetadata(metadata);

UploadPartResult uploadResult = s3Client.uploadPart(uploadRequest);

// Verify ETag
String expectedETag = DatatypeConverter.printHexBinary(part1Md5Bytes).toLowerCase();
assertEquals(expectedETag, uploadResult.getPartETag().getETag());

// Complete multipart upload
List<PartETag> partETags = new ArrayList<>();
partETags.add(uploadResult.getPartETag());

CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(
bucketName, keyName, uploadId, partETags);
s3Client.completeMultipartUpload(completeRequest);

// Verify object was uploaded
S3Object object = s3Client.getObject(bucketName, keyName);
try (S3ObjectInputStream s3is = object.getObjectContent();
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
IOUtils.copy(s3is, bos);
assertEquals(part1Content, bos.toString("UTF-8"));
}
}

@Test
public void testMultipartUploadPartWithWrongMD5Header() throws Exception {
final String bucketName = getBucketName();
final String keyName = getKeyName();
s3Client.createBucket(bucketName);

// Initiate multipart upload
InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, keyName);
InitiateMultipartUploadResult initResponse = s3Client.initiateMultipartUpload(initRequest);
String uploadId = initResponse.getUploadId();

// Prepare part data with wrong MD5
String partContent = "partdata";
byte[] partBytes = partContent.getBytes(StandardCharsets.UTF_8);

byte[] wrongMd5Bytes = calculateDigest(
new ByteArrayInputStream("wrongdata".getBytes(StandardCharsets.UTF_8)), 0, 9);
String wrongMd5Base64 = Base64.getEncoder().encodeToString(wrongMd5Bytes);

// Upload part with wrong MD5 should fail
InputStream partInputStream = new ByteArrayInputStream(partBytes);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentMD5(wrongMd5Base64);
metadata.setContentLength(partBytes.length);

UploadPartRequest uploadRequest = new UploadPartRequest()
.withBucketName(bucketName)
.withKey(keyName)
.withUploadId(uploadId)
.withPartNumber(1)
.withInputStream(partInputStream)
.withPartSize(partBytes.length)
.withObjectMetadata(metadata);

AmazonServiceException ase = assertThrows(AmazonServiceException.class,
() -> s3Client.uploadPart(uploadRequest));

assertEquals(ErrorType.Client, ase.getErrorType());
assertEquals(400, ase.getStatusCode());
assertEquals("BadDigest", ase.getErrorCode());

// Abort the multipart upload
AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(bucketName, keyName, uploadId);
s3Client.abortMultipartUpload(abortRequest);

// Verify object was not created
assertFalse(s3Client.doesObjectExist(bucketName, keyName));
}

@Test
public void testPutDoubleSlashPrefixObject() throws IOException {
final String bucketName = getBucketName();
Expand Down
Loading