Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple LocalS3 instance in the same JVM #18

Merged
merged 1 commit into from
Nov 25, 2022
Merged
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
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