Skip to content

Commit

Permalink
Support multiple LocalS3 instance in the same JVM (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
Robothy authored Nov 25, 2022
1 parent bbc8f12 commit b7a64a3
Show file tree
Hide file tree
Showing 33 changed files with 555 additions and 384 deletions.
1 change: 1 addition & 0 deletions local-s3-interationtest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
testImplementation (project(":local-s3-datatypes"))

testImplementation(project(":local-s3-core"))
testImplementation(project(":local-s3-rest"))

testRuntimeOnly "ch.qos.logback:logback-core:${libVersion['ch.qos.logback.logback-core']}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.robothy.s3.test;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.HeadBucketRequest;
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;
import com.robothy.s3.rest.LocalS3;
import com.robothy.s3.rest.bootstrap.LocalS3Mode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class MultiLocalS3InstanceIntegrationTest {

@Test
@DisplayName("Multiple LocalS3 instance in the same JVM")
void test() throws IOException {
Path dataPath = Files.createTempDirectory("local-s3");

LocalS3 persistenceInstance = LocalS3.builder()
.mode(LocalS3Mode.PERSISTENCE)
.dataPath(dataPath.toAbsolutePath().toString())
.port(-1)
.build();

persistenceInstance.start();
LocalS3 inMemoryInstance = LocalS3.builder()
.mode(LocalS3Mode.IN_MEMORY)
.port(-1)
.build();
inMemoryInstance.start();

AmazonS3 persistenceS3 = createClient(persistenceInstance.getPort());
AmazonS3 inMemoS3 = createClient(inMemoryInstance.getPort());
assertDoesNotThrow(() -> persistenceS3.createBucket("my-bucket"));
assertDoesNotThrow(() -> inMemoS3.createBucket("my-bucket"));

persistenceInstance.shutdown();
inMemoryInstance.shutdown();

LocalS3 inMemoWithInitial = LocalS3.builder().mode(LocalS3Mode.IN_MEMORY)
.dataPath(dataPath.toAbsolutePath().toString())
.port(-1)
.build();
inMemoWithInitial.start();
AmazonS3 client = createClient(inMemoWithInitial.getPort());
assertDoesNotThrow(() -> client.headBucket(new HeadBucketRequest("my-bucket")));

}

AmazonS3 createClient(int port) {
return AmazonS3ClientBuilder.standard().withEndpointConfiguration(
new AwsClientBuilder.EndpointConfiguration("http://localhost:" + port, "local"))
.enablePathStyleAccess()
.build();
}

}
19 changes: 9 additions & 10 deletions local-s3-jupiter/src/main/java/com/robothy/s3/jupiter/LocalS3.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,39 @@
*
* <p>Below example injects an {@code AmazonS3} instance to the parameter:
*
* <pre>
* &#064;LocalS3
* <pre>{@code
* @LocalS3
* class AppTest {
* &#064;Test
* @Test
* void test(AmazonS3 s3) {
* s3.createBucket("my-bucket");
* }
* }
* </pre>
* }</pre>
*
* Or resolve a {@linkplain LocalS3Endpoint}.
*
* <pre>
* <pre>{@code
* class AppTest {
* &#064;Test
* &#064;LocalS3
* @Test
* @LocalS3
* void test1(LocalS3Endpoint endpoint) {
* AmazonS3 client = AmazonS3ClientBuilder.standard()
* .enablePathStyleAccess()
* .withEndpointConfiguration(endpoint.toAmazonS3EndpointConfiguration())
* .build();
* assertDoesNotThrow(() -> client.createBucket("my-bucket"));
* }
*
* }
* </pre>
* }</pre>
*
* <p> If {@code @LocalS3} is on a test class, the Junit5 extension will create a shared
* service for all test methods in the class and shut it down in the "after all" callback.
* If {@code @LocalS3} is on a test method, the extension creates an exclusive service
* for the method and shut down the service in the "after each" callback.
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(LocalS3Extension.class)
@ExtendWith(AmazonS3Resolver.class)
Expand Down
26 changes: 13 additions & 13 deletions local-s3-rest/src/main/java/com/robothy/s3/rest/LocalS3.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.robothy.s3.core.service.manager.LocalS3Manager;
import com.robothy.s3.rest.bootstrap.LocalS3Mode;
import com.robothy.s3.rest.handler.LocalS3RouterFactory;
import com.robothy.s3.rest.service.DefaultServiceFactory;
import com.robothy.s3.rest.service.ServiceFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
Expand Down Expand Up @@ -80,47 +81,46 @@ public static Builder builder() {
*/
@SneakyThrows
public void start() {
registerServices();
ServiceFactory serviceFactory = createServiceFactory();

this.parentGroup = new NioEventLoopGroup(nettyParentEventGroupThreadNum);
this.childGroup = new NioEventLoopGroup(nettyChildEventGroupThreadNum);
this.executorGroup = new DefaultEventLoopGroup(s3ExecutorThreadNum);

ServerBootstrap serverBootstrap = new ServerBootstrap();
ChannelFuture channelFuture = serverBootstrap.group(parentGroup, childGroup)
.handler(new LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer(executorGroup, LocalS3RouterFactory.create()))
.childHandler(new HttpServerInitializer(executorGroup, LocalS3RouterFactory.create(serviceFactory)))
.bind(port)
.sync();
log.info("LocalS3 started.");
this.serverSocketChannel = channelFuture.channel();
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}

private void registerServices() {
private ServiceFactory createServiceFactory() {

LocalS3Manager manager;
if (mode == LocalS3Mode.IN_MEMORY) {
log.info("Created in-memory LocalS3 manager.");
LocalS3Manager.createInMemoryS3Manager(dataPath, initialDataCacheEnabled);
manager = LocalS3Manager.createInMemoryS3Manager(dataPath, initialDataCacheEnabled);
} else {
log.info("Created file system LocalS3 manager.");
LocalS3Manager.createFileSystemS3Manager(dataPath);
manager = LocalS3Manager.createFileSystemS3Manager(dataPath);
}

LocalS3Manager manager = mode == LocalS3Mode.IN_MEMORY ?
LocalS3Manager.createInMemoryS3Manager(dataPath, initialDataCacheEnabled) :
LocalS3Manager.createFileSystemS3Manager(dataPath);

ServiceFactory serviceFactory = new DefaultServiceFactory();
BucketService bucketService = manager.bucketService();
ObjectService objectService = manager.objectService();
ServiceFactory.register(BucketService.class, () -> bucketService);
ServiceFactory.register(ObjectService.class, () -> objectService);
serviceFactory.register(BucketService.class, () -> bucketService);
serviceFactory.register(ObjectService.class, () -> objectService);

XMLInputFactory input = new WstxInputFactory();
input.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
XmlMapper xmlMapper = new XmlMapper(new XmlFactory(input, new WstxOutputFactory()));
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ServiceFactory.register(XmlMapper.class, () -> xmlMapper);
serviceFactory.register(XmlMapper.class, () -> xmlMapper);
return serviceFactory;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void start() {
ChannelFuture channelFuture = serverBootstrap.group(parentGroup, childGroup)
.handler(new LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer(executorGroup, LocalS3RouterFactory.create()))
.childHandler(new HttpServerInitializer(executorGroup, LocalS3RouterFactory.create(null)))
.bind(options.getPort())
.sync();
this.serverSocketChannel = channelFuture.channel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@

class BucketPolicyController {

private final BucketPolicyService bucketPolicyService = ServiceFactory.getInstance(BucketService.class);
private final BucketPolicyService bucketPolicyService;

BucketPolicyController(ServiceFactory serviceFactory) {
this.bucketPolicyService = serviceFactory.getInstance(BucketService.class);
}

/**
* Handle <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicy.html">GetBucketPolicy</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.robothy.s3.core.model.request.CompleteMultipartUploadPartOption;
import com.robothy.s3.core.service.CompleteMultipartUploadService;
import com.robothy.s3.core.service.ObjectService;
import com.robothy.s3.core.util.JsonUtils;
import com.robothy.s3.rest.assertions.RequestAssertions;
import com.robothy.s3.rest.constants.AmzHeaderNames;
import com.robothy.s3.rest.model.request.CompleteMultipartUpload;
Expand All @@ -26,9 +25,14 @@
*/
class CompleteMultipartUploadController implements HttpRequestHandler {

private final CompleteMultipartUploadService uploadService = ServiceFactory.getInstance(ObjectService.class);
private final CompleteMultipartUploadService uploadService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

CompleteMultipartUploadController(ServiceFactory serviceFactory) {
this.uploadService = serviceFactory.getInstance(ObjectService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@
*/
class CopyObjectController implements HttpRequestHandler {

private final CopyObjectService objectService = ServiceFactory.getInstance(ObjectService.class);
private final CopyObjectService objectService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

CopyObjectController(ServiceFactory serviceFactory) {
this.objectService = serviceFactory.getInstance(ObjectService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@
@Slf4j
class CreateBucketController implements HttpRequestHandler {

BucketService bucketService = ServiceFactory.getInstance(BucketService.class);
BucketService bucketService;

XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
XmlMapper xmlMapper;

CreateBucketController(ServiceFactory serviceFactory) {
this.bucketService = serviceFactory.getInstance(BucketService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@
*/
class CreateMultipartUploadController implements HttpRequestHandler {

private final CreateMultipartUploadService uploadService = ServiceFactory.getInstance(ObjectService.class);
private final CreateMultipartUploadService uploadService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

CreateMultipartUploadController(ServiceFactory serviceFactory) {
this.uploadService = serviceFactory.getInstance(ObjectService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
*/
class DeleteBucketController implements HttpRequestHandler {

private final BucketService bucketService = ServiceFactory.getInstance(BucketService.class);
private final BucketService bucketService;

DeleteBucketController(ServiceFactory serviceFactory) {
this.bucketService = serviceFactory.getInstance(BucketService.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
*/
class DeleteBucketTaggingController implements HttpRequestHandler {

private final BucketService bucketService = ServiceFactory.getInstance(BucketService.class);
private final BucketService bucketService;

DeleteBucketTaggingController(ServiceFactory serviceFactory) {
this.bucketService = serviceFactory.getInstance(BucketService.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
*/
class DeleteObjectController implements HttpRequestHandler {

private final DeleteObjectService deleteObjectService = ServiceFactory.getInstance(ObjectService.class);
private final DeleteObjectService deleteObjectService;

DeleteObjectController(ServiceFactory serviceFactory) {
this.deleteObjectService = serviceFactory.getInstance(ObjectService.class);
}

@Override
public void handle(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.util.Random;

/**
* Handle any unhandled exceptions and construct an error response body.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@
*/
class GetBucketAclController implements HttpRequestHandler {

private final BucketAclService aclService = ServiceFactory.getInstance(BucketService.class);
private final BucketAclService aclService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

GetBucketAclController(ServiceFactory serviceFactory) {
this.aclService = serviceFactory.getInstance(BucketService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@
*/
class GetBucketController implements HttpRequestHandler {

private final BucketService bucketService = ServiceFactory.getInstance(BucketService.class);
private final BucketService bucketService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

GetBucketController(ServiceFactory serviceFactory) {
this.bucketService = serviceFactory.getInstance(BucketService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@
import com.robothy.s3.rest.utils.ResponseUtils;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
* Handle <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html">GetBucketTagging<a/>
*/
class GetBucketTaggingController implements HttpRequestHandler {

private final BucketService bucketService = ServiceFactory.getInstance(BucketService.class);
private final BucketService bucketService;

private final XmlMapper xmlMapper = ServiceFactory.getInstance(XmlMapper.class);
private final XmlMapper xmlMapper;

GetBucketTaggingController(ServiceFactory serviceFactory) {
this.bucketService = serviceFactory.getInstance(BucketService.class);
this.xmlMapper = serviceFactory.getInstance(XmlMapper.class);
}

@Override
public void handle(HttpRequest request, HttpResponse response) throws Exception {
Expand Down
Loading

0 comments on commit b7a64a3

Please sign in to comment.