merlimat closed pull request #1806: Use our own minimal S3Mock rather than 
findify S3Mock
URL: https://github.com/apache/incubator-pulsar/pull/1806
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/pom.xml b/pom.xml
index 4c8bebf7de..22506ca072 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,7 +154,6 @@ flexible messaging model and an intuitive client 
API.</description>
 
     <!-- test dependencies -->
     <disruptor.version>3.4.0</disruptor.version>
-    <s3mock.version>0.2.5</s3mock.version>
 
     <!-- Plugin dependencies -->
     <protobuf-maven-plugin.version>0.5.0</protobuf-maven-plugin.version>
@@ -729,13 +728,6 @@ flexible messaging model and an intuitive client 
API.</description>
         <artifactId>disruptor</artifactId>
         <version>${disruptor.version}</version>
       </dependency>
-
-      <dependency>
-        <groupId>io.findify</groupId>
-        <artifactId>s3mock_2.12</artifactId>
-        <version>${s3mock.version}</version>
-        <scope>test</scope>
-      </dependency>
     </dependencies>
   </dependencyManagement>
 
diff --git a/pulsar-broker/pom.xml b/pulsar-broker/pom.xml
index 600b374bf6..7ed7407ca0 100644
--- a/pulsar-broker/pom.xml
+++ b/pulsar-broker/pom.xml
@@ -148,12 +148,6 @@
       <artifactId>aws-java-sdk-s3</artifactId>
     </dependency>
 
-    <dependency>
-      <groupId>io.findify</groupId>
-      <artifactId>s3mock_2.12</artifactId>
-      <scope>test</scope>
-    </dependency>
-
     <!-- functions related dependencies (begin) -->
 
     <dependency>
diff --git 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloader.java
 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloader.java
index ee825323b0..27a0ac79ba 100644
--- 
a/pulsar-broker/src/main/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloader.java
+++ 
b/pulsar-broker/src/main/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloader.java
@@ -118,7 +118,7 @@ static String indexBlockOffloadKey(long ledgerId, UUID 
uuid) {
                 .withLedgerMetadata(readHandle.getLedgerMetadata());
             String dataBlockKey = dataBlockOffloadKey(readHandle.getId(), 
uuid);
             String indexBlockKey = indexBlockOffloadKey(readHandle.getId(), 
uuid);
-            InitiateMultipartUploadRequest dataBlockReq = new 
InitiateMultipartUploadRequest(bucket, dataBlockKey);
+            InitiateMultipartUploadRequest dataBlockReq = new 
InitiateMultipartUploadRequest(bucket, dataBlockKey, new ObjectMetadata());
             InitiateMultipartUploadResult dataBlockRes = null;
 
             // init multi part upload for data block.
@@ -172,9 +172,9 @@ static String indexBlockOffloadKey(long ledgerId, UUID 
uuid) {
                     .withUploadId(dataBlockRes.getUploadId())
                     .withPartETags(etags));
             } catch (Throwable t) {
+                promise.completeExceptionally(t);
                 s3client.abortMultipartUpload(
                     new AbortMultipartUploadRequest(bucket, dataBlockKey, 
dataBlockRes.getUploadId()));
-                promise.completeExceptionally(t);
                 return;
             }
 
@@ -191,8 +191,8 @@ static String indexBlockOffloadKey(long ledgerId, UUID 
uuid) {
                     metadata));
                 promise.complete(null);
             } catch (Throwable t) {
-                s3client.deleteObject(bucket, dataBlockKey);
                 promise.completeExceptionally(t);
+                s3client.deleteObject(bucket, dataBlockKey);
                 return;
             }
         });
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloaderTest.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloaderTest.java
index 5e465b6ab8..9f0d253ab1 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloaderTest.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3ManagedLedgerOffloaderTest.java
@@ -116,13 +116,8 @@ public void testHappyCase() throws Exception {
 
     @Test
     public void testBucketDoesNotExist() throws Exception {
-        ServiceConfiguration conf = new ServiceConfiguration();
-        
conf.setManagedLedgerOffloadDriver(S3ManagedLedgerOffloader.DRIVER_NAME);
-        conf.setS3ManagedLedgerOffloadBucket("no-bucket");
-        conf.setS3ManagedLedgerOffloadServiceEndpoint(s3endpoint);
-        conf.setS3ManagedLedgerOffloadRegion("eu-west-1");
-        LedgerOffloader offloader = S3ManagedLedgerOffloader.create(conf, 
scheduler);
-
+        LedgerOffloader offloader = new S3ManagedLedgerOffloader(s3client, 
"no-bucket", scheduler,
+                                                                 
DEFAULT_BLOCK_SIZE, DEFAULT_READ_BUFFER_SIZE);
         try {
             offloader.offload(buildReadHandle(), UUID.randomUUID(), new 
HashMap<>()).get();
             Assert.fail("Shouldn't be able to add to bucket");
@@ -383,11 +378,10 @@ public void testOffloadReadInvalidEntryIds() throws 
Exception {
 
     @Test
     public void testDeleteOffloaded() throws Exception {
-        int maxBlockSize = 1024;
-        int entryCount = 3;
-        ReadHandle readHandle = buildReadHandle(maxBlockSize, entryCount);
+        ReadHandle readHandle = buildReadHandle(DEFAULT_BLOCK_SIZE, 1);
         UUID uuid = UUID.randomUUID();
-        LedgerOffloader offloader = new S3ManagedLedgerOffloader(s3client, 
BUCKET, scheduler, maxBlockSize, DEFAULT_READ_BUFFER_SIZE);
+        LedgerOffloader offloader = new S3ManagedLedgerOffloader(s3client, 
BUCKET, scheduler,
+                                                                 
DEFAULT_BLOCK_SIZE, DEFAULT_READ_BUFFER_SIZE);
 
         // verify object exist after offload
         offloader.offload(readHandle, uuid, new HashMap<>()).get();
@@ -402,11 +396,10 @@ public void testDeleteOffloaded() throws Exception {
 
     @Test
     public void testDeleteOffloadedFail() throws Exception {
-        int maxBlockSize = 1024;
-        int entryCount = 3;
-        ReadHandle readHandle = buildReadHandle(maxBlockSize, entryCount);
+        ReadHandle readHandle = buildReadHandle(DEFAULT_BLOCK_SIZE, 1);
         UUID uuid = UUID.randomUUID();
-        LedgerOffloader offloader = new S3ManagedLedgerOffloader(s3client, 
BUCKET, scheduler, maxBlockSize, DEFAULT_READ_BUFFER_SIZE);
+        LedgerOffloader offloader = new S3ManagedLedgerOffloader(s3client, 
BUCKET, scheduler,
+                                                                 
DEFAULT_BLOCK_SIZE, DEFAULT_READ_BUFFER_SIZE);
         String failureString = "fail deleteOffloaded";
         AmazonS3 mockS3client = Mockito.spy(s3client);
         Mockito
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3Mock.java 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3Mock.java
new file mode 100644
index 0000000000..74d48e2be5
--- /dev/null
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3Mock.java
@@ -0,0 +1,334 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.broker.s3offload;
+
+import com.amazonaws.services.s3.AbstractAmazonS3;
+import com.amazonaws.services.s3.model.AmazonS3Exception;
+import com.amazonaws.services.s3.model.Bucket;
+import com.amazonaws.services.s3.model.CompleteMultipartUploadRequest;
+import com.amazonaws.services.s3.model.CompleteMultipartUploadResult;
+import com.amazonaws.services.s3.model.CopyObjectRequest;
+import com.amazonaws.services.s3.model.CopyObjectResult;
+import com.amazonaws.services.s3.model.DeleteObjectRequest;
+import com.amazonaws.services.s3.model.DeleteObjectsRequest;
+import com.amazonaws.services.s3.model.DeleteObjectsResult;
+import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
+import com.amazonaws.services.s3.model.GetObjectRequest;
+import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest;
+import com.amazonaws.services.s3.model.InitiateMultipartUploadResult;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PartETag;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import com.amazonaws.services.s3.model.PutObjectResult;
+import com.amazonaws.services.s3.model.S3Object;
+import com.amazonaws.services.s3.model.UploadPartRequest;
+import com.amazonaws.services.s3.model.UploadPartResult;
+
+import com.google.common.collect.ComparisonChain;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.stream.Collectors;
+
+/**
+ * Minimal mock for amazon client.
+ * If making any changes, validate they behave the same as S3 by running all 
S3 tests with -DtestRealAWS=true
+ */
+class S3Mock extends AbstractAmazonS3 {
+    @Override
+    public boolean doesBucketExistV2(String bucketName) {
+        return buckets.containsKey(bucketName);
+    }
+
+    @Override
+    public boolean doesObjectExist(String bucketName, String objectName) {
+        return buckets.containsKey(bucketName) && 
getBucket(bucketName).hasObject(objectName);
+    }
+
+    @Override
+    public Bucket createBucket(String bucketName) {
+        return buckets.computeIfAbsent(bucketName, (k) -> new MockBucket(k));
+    }
+
+    private MockBucket getBucket(String bucketName) throws AmazonS3Exception {
+        MockBucket bucket = buckets.get(bucketName);
+        if (bucket != null) {
+            return bucket;
+        } else {
+            throw new AmazonS3Exception("NoSuchBucket: Bucket doesn't exist");
+        }
+    }
+
+    @Override
+    public PutObjectResult putObject(PutObjectRequest putObjectRequest)
+            throws AmazonS3Exception {
+        return 
getBucket(putObjectRequest.getBucketName()).putObject(putObjectRequest);
+    }
+
+    @Override
+    public S3Object getObject(GetObjectRequest getObjectRequest) {
+        return 
getBucket(getObjectRequest.getBucketName()).getObject(getObjectRequest);
+    }
+
+    @Override
+    public ObjectMetadata getObjectMetadata(GetObjectMetadataRequest 
getObjectMetadataRequest)
+            throws AmazonS3Exception {
+        return 
getBucket(getObjectMetadataRequest.getBucketName()).getObjectMetadata(getObjectMetadataRequest);
+    }
+
+    @Override
+    public void deleteObject(DeleteObjectRequest deleteObjectRequest)
+            throws AmazonS3Exception {
+        
getBucket(deleteObjectRequest.getBucketName()).deleteObject(deleteObjectRequest.getKey());
+    }
+
+    @Override
+    public DeleteObjectsResult deleteObjects(DeleteObjectsRequest 
deleteObjectsRequest)
+            throws AmazonS3Exception {
+        List<DeleteObjectsResult.DeletedObject> results = 
deleteObjectsRequest.getKeys().stream().map((k) -> {
+                
getBucket(deleteObjectsRequest.getBucketName()).deleteObject(k.getKey());
+                DeleteObjectsResult.DeletedObject res = new 
DeleteObjectsResult.DeletedObject();
+                res.setKey(k.getKey());
+                return res;
+            }).collect(Collectors.toList());
+        return new DeleteObjectsResult(results);
+    }
+
+    @Override
+    public CopyObjectResult copyObject(CopyObjectRequest copyObjectRequest)
+            throws AmazonS3Exception {
+        S3Object from = getObject(new 
GetObjectRequest(copyObjectRequest.getSourceBucketName(),
+                                                       
copyObjectRequest.getSourceKey()));
+        ObjectMetadata newMetadata = copyObjectRequest.getNewObjectMetadata();
+        if (newMetadata == null) {
+            newMetadata = from.getObjectMetadata();
+        }
+        
newMetadata.setContentLength(from.getObjectMetadata().getContentLength());
+        putObject(new 
PutObjectRequest(copyObjectRequest.getDestinationBucketName(),
+                                       copyObjectRequest.getDestinationKey(),
+                                       from.getObjectContent(),
+                                       newMetadata));
+        return new CopyObjectResult();
+    }
+
+    @Override
+    public InitiateMultipartUploadResult 
initiateMultipartUpload(InitiateMultipartUploadRequest request)
+            throws AmazonS3Exception {
+        return getBucket(request.getBucketName()).initMultipart(request);
+    }
+
+    @Override
+    public UploadPartResult uploadPart(UploadPartRequest request)
+            throws AmazonS3Exception {
+        return getBucket(request.getBucketName()).uploadPart(request);
+    }
+
+    @Override
+    public CompleteMultipartUploadResult 
completeMultipartUpload(CompleteMultipartUploadRequest request)
+            throws AmazonS3Exception {
+        return getBucket(request.getBucketName()).completeMultipart(request);
+    }
+
+    ConcurrentHashMap<String, MockBucket> buckets = new ConcurrentHashMap<>();
+
+    static class MockBucket extends Bucket {
+        ConcurrentHashMap<String, MockObject> objects = new 
ConcurrentHashMap<>();
+        ConcurrentHashMap<String, MockMultipart> inprogressMultipart = new 
ConcurrentHashMap<>();
+
+        MockBucket(String name) {
+            super(name);
+        }
+
+        boolean hasObject(String key) {
+            return objects.containsKey(key);
+        }
+
+        PutObjectResult putObject(PutObjectRequest putObjectRequest) throws 
AmazonS3Exception {
+            byte[] bytes = streamToBytes(putObjectRequest.getInputStream(),
+                                         
(int)putObjectRequest.getMetadata().getContentLength());
+            objects.put(putObjectRequest.getKey(),
+                        new MockObject(putObjectRequest.getMetadata(), bytes));
+            return new PutObjectResult();
+        }
+
+        S3Object getObject(GetObjectRequest getObjectRequest) throws 
AmazonS3Exception {
+            MockObject obj = objects.get(getObjectRequest.getKey());
+            if (obj == null) {
+                throw new AmazonS3Exception("Object doesn't exist");
+            }
+
+            S3Object s3obj = new S3Object();
+            s3obj.setBucketName(getObjectRequest.getBucketName());
+            s3obj.setKey(getObjectRequest.getKey());
+
+            if (getObjectRequest.getRange() != null) {
+                long[] range = getObjectRequest.getRange();
+                int size = (int)(range[1] - range[0] + 1);
+                ObjectMetadata metadata = obj.metadata.clone();
+                metadata.setHeader("Content-Range",
+                                   String.format("bytes %d-%d/%d",
+                                                 range[0], range[1], size));
+                s3obj.setObjectMetadata(metadata);
+                s3obj.setObjectContent(new ByteArrayInputStream(obj.data, 
(int)range[0], size));
+                return s3obj;
+            } else {
+                s3obj.setObjectMetadata(obj.metadata);
+                s3obj.setObjectContent(new ByteArrayInputStream(obj.data));
+                return s3obj;
+            }
+        }
+
+        void deleteObject(String key) {
+            objects.remove(key);
+        }
+
+        ObjectMetadata getObjectMetadata(GetObjectMetadataRequest 
getObjectMetadataRequest)
+                throws AmazonS3Exception {
+            MockObject obj = objects.get(getObjectMetadataRequest.getKey());
+            if (obj == null) {
+                throw new AmazonS3Exception("Object doesn't exist");
+            }
+            return obj.metadata;
+        }
+
+        InitiateMultipartUploadResult 
initMultipart(InitiateMultipartUploadRequest request)
+                throws AmazonS3Exception {
+            String uploadId = UUID.randomUUID().toString();
+            inprogressMultipart.put(uploadId, new 
MockMultipart(request.getKey(),
+                                                                
request.getObjectMetadata()));
+            InitiateMultipartUploadResult result = new 
InitiateMultipartUploadResult();
+            result.setBucketName(request.getBucketName());
+            result.setKey(request.getKey());
+            result.setUploadId(uploadId);
+            return result;
+        }
+
+        MockMultipart getMultipart(String uploadId, String key) throws 
AmazonS3Exception {
+            MockMultipart multi = inprogressMultipart.get(uploadId);
+            if (multi == null) {
+                throw new AmazonS3Exception("No such upload " + uploadId);
+            }
+            if (!multi.key.equals(key)) {
+                throw new AmazonS3Exception("Wrong key for upload " + uploadId
+                                            + ", expected " + key
+                                            + ", got " + multi.key);
+            }
+            return multi;
+        }
+
+        UploadPartResult uploadPart(UploadPartRequest request)
+                throws AmazonS3Exception {
+            MockMultipart multi = getMultipart(request.getUploadId(), 
request.getKey());
+            byte[] bytes = streamToBytes(request.getInputStream(), 
(int)request.getPartSize());
+            UploadPartResult result = new UploadPartResult();
+            result.setPartNumber(request.getPartNumber());
+            result.setETag(multi.addPart(request.getPartNumber(), bytes));
+            return result;
+        }
+
+        CompleteMultipartUploadResult 
completeMultipart(CompleteMultipartUploadRequest request)
+                throws AmazonS3Exception {
+            MockMultipart multi = getMultipart(request.getUploadId(), 
request.getKey());
+            inprogressMultipart.remove(request.getUploadId());
+            objects.put(request.getKey(), 
multi.complete(request.getPartETags()));
+            CompleteMultipartUploadResult result = new 
CompleteMultipartUploadResult();
+            result.setBucketName(request.getBucketName());
+            result.setKey(request.getKey());
+            return result;
+        }
+    }
+
+    private static byte[] streamToBytes(InputStream data, int length) throws 
AmazonS3Exception {
+        byte[] bytes = new byte[length];
+        try {
+            for (int i = 0; i < length; i++) {
+                bytes[i] = (byte)data.read();
+            }
+        } catch (IOException ioe) {
+            throw new AmazonS3Exception("Error loading data", ioe);
+        }
+        return bytes;
+    }
+
+    static class MockObject {
+        final ObjectMetadata metadata;
+        final byte[] data;
+        final Map<Integer, long[]> partRanges;
+
+
+        MockObject(ObjectMetadata metadata, byte[] data) {
+            this(metadata, data, null);
+        }
+
+        MockObject(ObjectMetadata metadata, byte[] data, Map<Integer, long[]> 
partRanges) {
+            this.metadata = metadata;
+            this.data = data;
+            this.partRanges = partRanges;
+        }
+
+    }
+
+    static class MockMultipart {
+        final String key;
+        final ObjectMetadata metadata;
+        final ConcurrentSkipListMap<PartETag, byte[]> parts = new 
ConcurrentSkipListMap<>(
+                (etag1, etag2) -> 
ComparisonChain.start().compare(etag1.getPartNumber(),
+                                                                  
etag2.getPartNumber()).result());
+
+        MockMultipart(String key, ObjectMetadata metadata) {
+            this.key = key;
+            this.metadata = metadata;
+        }
+
+        String addPart(int partNumber, byte[] bytes) {
+            String etag = UUID.randomUUID().toString();
+            parts.put(new PartETag(partNumber, etag), bytes);
+            return etag;
+        }
+
+        MockObject complete(List<PartETag> tags) throws AmazonS3Exception {
+            if (parts.size() != tags.size()
+                || !parts.keySet().containsAll(tags)) {
+                throw new AmazonS3Exception("Tags don't match uploaded parts");
+            }
+
+            int totalSize = parts.values().stream().map(v -> 
v.length).reduce(0, (acc, v) -> acc + v);
+            byte[] full = new byte[totalSize];
+
+            Map<Integer, long[]> partRanges = new HashMap<>();
+            int start = 0;
+            for (Map.Entry<PartETag, byte[]> e : parts.entrySet()) {
+                int partLength = e.getValue().length;
+                System.arraycopy(e.getValue(), 0, full, start, partLength);
+                partRanges.put(e.getKey().getPartNumber(),
+                               new long[] { start, start + partLength - 1 });
+                start += partLength;
+            }
+            metadata.setContentLength(totalSize);
+            return new MockObject(metadata, full, partRanges);
+        }
+    }
+}
diff --git 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3TestBase.java
 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3TestBase.java
index 2c65c0cd50..b56c8502da 100644
--- 
a/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3TestBase.java
+++ 
b/pulsar-broker/src/test/java/org/apache/pulsar/broker/s3offload/S3TestBase.java
@@ -24,32 +24,20 @@
 import com.amazonaws.services.s3.AmazonS3;
 import com.amazonaws.services.s3.AmazonS3ClientBuilder;
 
-import io.findify.s3mock.S3Mock;
-
 import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.AfterMethod;
 
 public class S3TestBase {
     final static String BUCKET = "pulsar-unittest";
 
-    S3Mock s3mock = null;
     protected AmazonS3 s3client = null;
-    protected String s3endpoint = null;
 
     @BeforeMethod
     public void start() throws Exception {
-        s3mock = new 
S3Mock.Builder().withPort(0).withInMemoryBackend().build();
-        int port = s3mock.start().localAddress().getPort();
-        s3endpoint = "http://localhost:"; + port;
-
         if (Boolean.parseBoolean(System.getProperty("testRealAWS", "false"))) {
             // To use this, ~/.aws must be configured with credentials and a 
default region
             s3client = AmazonS3ClientBuilder.standard().build();
         } else {
-            s3client = AmazonS3ClientBuilder.standard()
-                .withEndpointConfiguration(new 
EndpointConfiguration(s3endpoint, "foobar"))
-                .withCredentials(new AWSStaticCredentialsProvider(new 
AnonymousAWSCredentials()))
-                .withPathStyleAccessEnabled(true).build();
+            s3client = new S3Mock();
         }
 
         if (!s3client.doesBucketExistV2(BUCKET)) {
@@ -57,10 +45,4 @@ public void start() throws Exception {
         }
     }
 
-    @AfterMethod
-    public void stop() throws Exception {
-        if (s3mock != null) {
-            s3mock.shutdown();
-        }
-    }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to