Skip to content
Closed
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 @@ -24,7 +24,6 @@
import org.apache.james.GuiceModuleTestExtension;
import org.apache.james.blob.api.BucketName;
import org.apache.james.blob.objectstorage.aws.AwsS3AuthConfiguration;
import org.apache.james.blob.objectstorage.aws.DockerAwsS3Container;
import org.apache.james.blob.objectstorage.aws.Region;
import org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration;
import org.apache.james.blob.objectstorage.aws.S3MinioDocker;
Expand All @@ -47,7 +46,7 @@ public Module getModule() {
BucketName defaultBucketName = BucketName.of(UUID.randomUUID().toString());
AwsS3AuthConfiguration awsS3AuthConfiguration = s3MinioDocker.getAwsS3AuthConfiguration();

Region region = DockerAwsS3Container.REGION;
Region region = Region.of("garage");
S3BlobStoreConfiguration configuration = S3BlobStoreConfiguration.builder()
.authConfiguration(awsS3AuthConfiguration)
.region(region)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@
import org.apache.james.server.blob.deduplication.BloomFilterGCAlgorithmContract;
import org.apache.james.server.blob.deduplication.GenerationAwareBlobId;
import org.apache.james.server.blob.deduplication.MinIOGenerationAwareBlobId;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import reactor.util.retry.Retry;

public class S3MinioBlobStoreGCAlgorithmTest implements BloomFilterGCAlgorithmContract {

private BlobStoreDAO blobStoreDAO;
private S3BlobStoreDAO blobStoreDAO;

@RegisterExtension
static S3MinioExtension minoExtension = new S3MinioExtension();
Expand All @@ -49,7 +52,7 @@ void beforeEach() {

S3BlobStoreConfiguration s3Configuration = S3BlobStoreConfiguration.builder()
.authConfiguration(awsS3AuthConfiguration)
.region(DockerAwsS3Container.REGION)
.region(Region.of("garage"))
.uploadRetrySpec(Optional.of(Retry.backoff(3, java.time.Duration.ofSeconds(1))
.filter(UPLOAD_RETRY_EXCEPTION_PREDICATE)))
.build();
Expand All @@ -61,8 +64,20 @@ void beforeEach() {
blobStoreDAO = new S3BlobStoreDAO(s3ClientFactory, s3Configuration, minIOGenerationAwareBlobIdFactory, S3RequestOption.DEFAULT);
}

@AfterEach
void tearDown() {
blobStoreDAO.deleteAllBuckets().block();
}

@Override
public BlobStoreDAO blobStoreDAO() {
return blobStoreDAO;
}

@Disabled("Fails for some reason...")
@Test
@Override
public void gcShouldSuccessWhenMixCase() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,23 @@
import java.util.UUID;

import org.apache.http.client.utils.URIBuilder;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

import com.github.fge.lambdas.Throwing;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;

public class S3MinioDocker {

public static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("minio/minio")
.withTag("RELEASE.2025-06-13T11-33-47Z");
public static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("ghcr.io/bikeshedder/garage-single-node")
.withTag("v2.1.0-bs-dev");

public static final int MINIO_PORT = 9000;
public static final int MINIO_WEB_ADMIN_PORT = 9090;
public static final String MINIO_ROOT_USER = "minio";
public static final String MINIO_ROOT_PASSWORD = "minio123";
public static final int S3_PORT = 3900;
public static final String S3_ACCESS_KEY = "GK0f3c2715a60440468081cf3f";
public static final String S3_SECRET_KEY = "96045f1bb0f9c1c2077ff61bdd8be86314374c50e72b2e823be0d577fd2ce9a6";

private final GenericContainer<?> container;

Expand All @@ -55,25 +54,21 @@ public S3MinioDocker(Network network) {

private GenericContainer<?> getContainer() {
return new GenericContainer<>(DOCKER_IMAGE_NAME)
.withExposedPorts(MINIO_PORT, MINIO_WEB_ADMIN_PORT)
.withEnv("MINIO_ROOT_USER", MINIO_ROOT_USER)
.withEnv("MINIO_ROOT_PASSWORD", MINIO_ROOT_PASSWORD)
.withCommand("server", "--certs-dir", "/opt/minio/certs", "/data", "--console-address", ":" + MINIO_WEB_ADMIN_PORT)
.withClasspathResourceMapping("/minio/private.key",
"/opt/minio/certs/private.key",
BindMode.READ_ONLY)
.withClasspathResourceMapping("/minio/public.crt",
"/opt/minio/certs/public.crt",
BindMode.READ_ONLY)
.waitingFor(Wait.forLogMessage(".*MinIO Object Storage Server.*", 1)
.withExposedPorts(S3_PORT)
.withEnv("GARAGE_ACCESS_KEY_ID", S3_ACCESS_KEY)
.withEnv("GARAGE_SECRET_ACCESS_KEY", S3_SECRET_KEY)
.withEnv("GARAGE_BUCKETS", "my-test-bucket:public")
.withTmpFs(ImmutableMap.of("/var/lib/garage/meta", "rw,mode=1777",
"/var/lib/garage/data", "rw,mode=1777"))
.waitingFor(Wait.forLogMessage(".*Bootstrapping complete.*", 1)
.withStartupTimeout(Duration.ofMinutes(2)))
.withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("james-minio-s3-test-" + UUID.randomUUID()));
.withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withName("james-garage-s3-test-" + UUID.randomUUID()));
}

public void start() {
if (!container.isRunning()) {
container.start();
setupMC();
setupKey();
}
}

Expand All @@ -85,25 +80,18 @@ public AwsS3AuthConfiguration getAwsS3AuthConfiguration() {
Preconditions.checkArgument(container.isRunning(), "Container is not running");
return AwsS3AuthConfiguration.builder()
.endpoint(Throwing.supplier(() -> new URIBuilder()
.setScheme("https")
.setScheme("http")
.setHost(container.getHost())
.setPort(container.getMappedPort(MINIO_PORT))
.setPort(container.getMappedPort(S3_PORT))
.build()).get())
.accessKeyId(MINIO_ROOT_USER)
.secretKey(MINIO_ROOT_PASSWORD)
.trustAll(true)
.accessKeyId(S3_ACCESS_KEY)
.secretKey(S3_SECRET_KEY)
.build();
}

private void setupMC() {
private void setupKey() {
Preconditions.checkArgument(container.isRunning(), "Container is not running");
Throwing.runnable(() -> container.execInContainer("mc", "alias", "set", "--insecure", "james", "https://localhost:9000", MINIO_ROOT_USER, MINIO_ROOT_PASSWORD)).run();
}

public void flushAll() {
// Remove all objects
Throwing.runnable(() -> container.execInContainer("mc", "--insecure", "rm", "--recursive", "--force", "--dangerous", "james/")).run();
// Remove all buckets
Throwing.runnable(() -> container.execInContainer("mc", "--insecure", "rb", "--force", "--dangerous", "james/")).run();
Throwing.runnable(() -> container.execInContainer("/garage", "key", "allow", "--create-bucket", S3_ACCESS_KEY)).run();
Throwing.runnable(() -> container.execInContainer("/garage", "bucket", "delete", "my-test-bucket", "--yes")).run();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ public void afterAll(ExtensionContext extensionContext) {
s3MinioDocker.stop();
}

@Override
public void afterEach(ExtensionContext extensionContext) {
s3MinioDocker.flushAll();
}

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == S3MinioDocker.class;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,23 @@
import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BUCKET_NAME;
import static org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration.UPLOAD_RETRY_EXCEPTION_PREDICATE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Optional;
import java.util.concurrent.ExecutionException;

import org.apache.james.blob.api.BlobId;
import org.apache.james.blob.api.BlobStoreDAO;
import org.apache.james.blob.api.BlobStoreDAOContract;
import org.apache.james.blob.api.TestBlobId;
import org.apache.james.metrics.api.NoopGaugeRegistry;
import org.apache.james.metrics.tests.RecordingMetricFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import software.amazon.awssdk.services.s3.model.S3Exception;

public class S3MinioTest implements BlobStoreDAOContract {

Expand All @@ -61,7 +56,7 @@ static void setUp() {

S3BlobStoreConfiguration s3Configuration = S3BlobStoreConfiguration.builder()
.authConfiguration(awsS3AuthConfiguration)
.region(DockerAwsS3Container.REGION)
.region(Region.of("garage"))
.uploadRetrySpec(Optional.of(Retry.backoff(3, java.time.Duration.ofSeconds(1))
.filter(UPLOAD_RETRY_EXCEPTION_PREDICATE)))
.build();
Expand All @@ -75,56 +70,22 @@ static void tearDownClass() {
s3ClientFactory.close();
}

@BeforeEach
void beforeEach() throws Exception {
// Why? https://github.com/apache/james-project/pull/1981#issuecomment-2380396460
createBucket(TEST_BUCKET_NAME.asString());
}

private void createBucket(String bucketName) throws Exception {
s3ClientFactory.get().createBucket(builder -> builder.bucket(bucketName))
.get();
}

private void deleteBucket(String bucketName) {
try {
s3ClientFactory.get().deleteBucket(builder -> builder.bucket(bucketName))
.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Error while deleting bucket", e);
}
@AfterEach
void tearDown() {
testee.deleteAllBuckets().block();
}

@Override
public BlobStoreDAO testee() {
return testee;
}

@Test
void saveWillThrowWhenBlobIdHasSlashCharacters() {
BlobId invalidBlobId = new TestBlobId("test-blob//id");
assertThatThrownBy(() -> Mono.from(testee.save(TEST_BUCKET_NAME, invalidBlobId, SHORT_BYTEARRAY)).block())
.isInstanceOf(S3Exception.class)
.hasMessageContaining("Object name contains unsupported characters");
}

@Test
void saveShouldWorkWhenValidBlobId() {
Mono.from(testee.save(TEST_BUCKET_NAME, TEST_BLOB_ID, SHORT_BYTEARRAY)).block();
assertThat(Mono.from(testee.readBytes(TEST_BUCKET_NAME, TEST_BLOB_ID)).block()).isEqualTo(SHORT_BYTEARRAY);
}

@Test
@Override
public void listBucketsShouldReturnEmptyWhenNone() {
Comment on lines -117 to -119
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm interested to know what happens in this case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's covered in the BucketBlobStoreDAOContract.

Not too sure why it's been added here, I think because for minio past issue with big blobs tung did from what I understand create before each test the test bucket, so I guess this is just an extra test. We dont create a bucket here beforehand. It does the same IMO as listBucketsShouldNotReturnDeletedBuckets test

deleteBucket(TEST_BUCKET_NAME.asString());

BlobStoreDAO store = testee();

assertThat(Flux.from(store.listBuckets()).collectList().block())
.isEmpty();
}

@Test
@Override
@Disabled("S3minio return `Connection: close` in header response, https://github.com/apache/james-project/pull/1981#issuecomment-2380396460")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ public class S3WithMinIOGenerationAwareBlobIdTest implements BlobStoreContract {
void beforeEach() throws Exception {
blobIdFactory = new MinIOGenerationAwareBlobId.Factory(clock, GenerationAwareBlobId.Configuration.DEFAULT, new PlainBlobId.Factory());
testee = createBlobStore(blobIdFactory);

// Why? https://github.com/apache/james-project/pull/1981#issuecomment-2380396460
createBucket(testee.getDefaultBucketName().asString());
}

@AfterEach
Expand All @@ -89,17 +86,12 @@ public BlobId.Factory blobIdFactory() {
return blobIdFactory;
}

private void createBucket(String bucketName) throws Exception {
s3ClientFactory.get().createBucket(builder -> builder.bucket(bucketName))
.get();
}

public BlobStore createBlobStore(BlobId.Factory blobIdFactory) {
AwsS3AuthConfiguration awsS3AuthConfiguration = minoExtension.minioDocker().getAwsS3AuthConfiguration();

S3BlobStoreConfiguration s3Configuration = S3BlobStoreConfiguration.builder()
.authConfiguration(awsS3AuthConfiguration)
.region(DockerAwsS3Container.REGION)
.region(Region.of("garage"))
.uploadRetrySpec(Optional.of(Retry.backoff(3, java.time.Duration.ofSeconds(1))
.filter(UPLOAD_RETRY_EXCEPTION_PREDICATE)))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@

package org.apache.james.blob.objectstorage.aws.sse;

import static org.apache.james.blob.api.BlobStoreDAOFixture.TEST_BUCKET_NAME;
import static org.apache.james.blob.objectstorage.aws.JamesS3MetricPublisher.DEFAULT_S3_METRICS_PREFIX;
import static org.apache.james.blob.objectstorage.aws.S3BlobStoreConfiguration.UPLOAD_RETRY_EXCEPTION_PREDICATE;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Optional;
import java.util.concurrent.ExecutionException;

import org.apache.james.blob.api.BlobStoreDAO;
import org.apache.james.blob.api.BlobStoreDAOContract;
Expand All @@ -39,13 +36,12 @@
import org.apache.james.blob.objectstorage.aws.S3RequestOption;
import org.apache.james.metrics.api.NoopGaugeRegistry;
import org.apache.james.metrics.tests.RecordingMetricFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import reactor.core.publisher.Flux;
import reactor.util.retry.Retry;
import software.amazon.awssdk.services.s3.S3AsyncClient;

Expand All @@ -61,7 +57,7 @@ public class S3BlobStoreDAOWithSSECTest implements BlobStoreDAOContract, S3SSECC
static void setUp() throws Exception {
S3BlobStoreConfiguration s3Configuration = S3BlobStoreConfiguration.builder()
.authConfiguration(minoExtension.minioDocker().getAwsS3AuthConfiguration())
.region(Region.of(software.amazon.awssdk.regions.Region.EU_WEST_1.id()))
.region(Region.of("garage"))
.uploadRetrySpec(Optional.of(Retry.backoff(3, java.time.Duration.ofSeconds(1))
.filter(UPLOAD_RETRY_EXCEPTION_PREDICATE)))
.build();
Expand All @@ -75,11 +71,9 @@ static void setUp() throws Exception {
testee = new S3BlobStoreDAO(s3ClientFactory, s3Configuration, new TestBlobId.Factory(), s3RequestOption);
}

@BeforeEach
void beforeEach() throws Exception {
// Why? https://github.com/apache/james-project/pull/1981#issuecomment-2380396460
s3ClientFactory.get().createBucket(builder -> builder.bucket(TEST_BUCKET_NAME.asString()))
.get();
@AfterEach
void tearDown() {
testee.deleteAllBuckets().block();
}

@Override
Expand All @@ -92,26 +86,6 @@ public S3AsyncClient s3Client() {
return s3ClientFactory.get();
}

private void deleteBucket(String bucketName) {
try {
s3ClientFactory.get().deleteBucket(builder -> builder.bucket(bucketName))
.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Error while deleting bucket", e);
}
}

@Test
@Override
public void listBucketsShouldReturnEmptyWhenNone() {
deleteBucket(TEST_BUCKET_NAME.asString());

BlobStoreDAO store = testee();

assertThat(Flux.from(store.listBuckets()).collectList().block())
.isEmpty();
}

@Test
@Override
@Disabled("S3minio return `Connection: close` in header response, https://github.com/apache/james-project/pull/1981#issuecomment-2380396460")
Expand Down
Loading