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
46 changes: 46 additions & 0 deletions docs/modules/ceph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Ceph Module

!!! note
This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.

Testcontainers module for [Ceph](https://ceph.io/).

## Ceph vs Localstack for simulating S3

One possible usage for the Ceph module is to take advantage of its S3 compatibility.

For simulating S3 you may wish to also consider the [Localstack](./localstack.md) module.
Ceph and Localstack provide good compatibility with the S3 API, and we encourage you to evaluate both.

Localstack is likely to be a better choice if you require simulation of other AWS services along with S3.

## Usage example

Creating a Ceph container:

<!--codeinclude-->
[Creating a Ceph container](../../modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java) inside_block:creating_container
<!--/codeinclude-->

Configuring an S3 Client to use Ceph:

<!--codeinclude-->
[Configuring an S3 Client to use Ceph](../../modules/ceph/src/test/java/org/testcontainers/containers/CephContainerTest.java) inside_block:setting_up_s3_client
<!--/codeinclude-->

## Adding this module to your project dependencies

Add the following dependency to your `pom.xml`/`build.gradle` file:

```groovy tab='Gradle'
testCompile "org.testcontainers:ceph:{{latest_version}}"
```

```xml tab='Maven'
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>ceph</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```
9 changes: 9 additions & 0 deletions docs/modules/localstack.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Testcontainers module for the Atlassian's [LocalStack](https://github.com/localstack/localstack), 'a fully functional local AWS cloud stack'.

## Ceph vs Localstack for simulating S3

One possible usage for the Localstack module is to take advantage of its S3 compatibility.

For simulating S3 you may wish to also consider the [Ceph](./ceph.md) module.
Ceph and Localstack provide good compatibility with the S3 API, and we encourage you to evaluate both.

Localstack is likely to be a better choice if you require simulation of other AWS services along with S3.

## Usage example

Running LocalStack as a stand-in for AWS S3 during a test:
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ nav:
- modules/databases/orientdb.md
- modules/databases/postgres.md
- modules/databases/presto.md
- modules/ceph.md
- modules/docker_compose.md
- modules/elasticsearch.md
- modules/kafka.md
Expand Down
7 changes: 7 additions & 0 deletions modules/ceph/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
description = "Testcontainers :: Ceph"

dependencies {
compile project(':testcontainers')

provided 'com.amazonaws:aws-java-sdk-s3:1.11.495'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.testcontainers.containers;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;

import java.time.Duration;

@Slf4j
@Getter
public class CephContainer extends GenericContainer<CephContainer> {

public static final String IMAGE = "ceph/daemon";
public static final String DEFAULT_TAG = "v3.2.13-stable-3.2-mimic-centos-7";

public static final int S3_PORT = 8080;
public static final int REST_API_PORT = 5000;

private String awsAccessKey = "ceph";

private String awsSecretKey = "ceph";

private String bucketName = "CEPH";

private String rgwName = "localhost";

private String demoUid = "ceph";

private NetworkAutoDetectMode networkAutoDetectMode = NetworkAutoDetectMode.IPV4_ONLY;

public CephContainer() {
super(IMAGE + ":" + DEFAULT_TAG);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
super(IMAGE + ":" + DEFAULT_TAG);
this(IMAGE + ":" + DEFAULT_TAG);

}

public CephContainer(String dockerImageName) {
super(dockerImageName);
}

@Override
protected void configure() {
withEnv("RGW_NAME", rgwName);
withEnv("NETWORK_AUTO_DETECT", networkAutoDetectMode.value);
withEnv("CEPH_DAEMON", "demo");
withEnv("CEPH_DEMO_UID", demoUid);
withEnv("CEPH_DEMO_ACCESS_KEY", awsAccessKey);
withEnv("CEPH_DEMO_SECRET_KEY", awsSecretKey);
withEnv("CEPH_DEMO_BUCKET", bucketName);
withExposedPorts(REST_API_PORT, S3_PORT);
waitingFor(
Copy link
Member

Choose a reason for hiding this comment

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

I'd move "non-dynamic" config to the constructor, otherwise the users won't be able to override it

new HttpWaitStrategy()
.forPath("/")
.forPort(REST_API_PORT)
.forStatusCode(200)
.withStartupTimeout(Duration.ofMinutes(5))
);
withLogConsumer(new Slf4jLogConsumer(log));
}

public AWSCredentialsProvider getAWSCredentialsProvider() {
return new AWSStaticCredentialsProvider(
new BasicAWSCredentials(awsAccessKey, awsSecretKey)
);
}

public AwsClientBuilder.EndpointConfiguration getAWSEndpointConfiguration() {
Copy link
Member

Choose a reason for hiding this comment

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

IIRC we agreed not to use 3rd party types in our public API (plus, I believe it may not work with AWS SDK v2)

return new AwsClientBuilder.EndpointConfiguration(
getContainerIpAddress() + ":" + getMappedPort(S3_PORT),
"us-east-1"
);
}

public CephContainer withAwsAccessKey(String awsAccessKey) {
this.awsAccessKey = awsAccessKey;
return self();
}

public CephContainer withAwsSecretKey(String awsSecretKey) {
this.awsSecretKey = awsSecretKey;
return self();
}

public CephContainer withBucketName(String bucketName) {
//because s3cmd transforming bucket name to uppercase
this.bucketName = bucketName.toUpperCase();
return self();
}

public CephContainer withRgwName(String rgwName) {
this.rgwName = rgwName;
return self();
}

public CephContainer withDemoUid(String demoUid) {
this.demoUid = demoUid;
return self();
}

public CephContainer withNetworkAutoDetectMode(NetworkAutoDetectMode networkAutoDetectMode) {
this.networkAutoDetectMode = networkAutoDetectMode;
return self();
}

@AllArgsConstructor
public enum NetworkAutoDetectMode {
IPV6_OR_IPV4("1"),
IPV4_ONLY("4"),
IPV6_ONLY("6");

private final String value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.testcontainers.containers;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.S3Object;
import org.apache.commons.io.IOUtils;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class CephContainerTest {
@Test
public void testS3Bucket() {
try (
// creating_container {
CephContainer cephContainer = new CephContainer()
.withAwsAccessKey("test")
.withAwsSecretKey("test")
.withBucketName("test");
// }
) {
cephContainer.start();

final AmazonS3 amazonS3 = buildS3Client(cephContainer);

String bucketName = "test2";

//check current bucket
assertTrue(amazonS3.doesBucketExistV2(cephContainer.getBucketName()));

//create another bucket
amazonS3.createBucket(bucketName);
assertTrue(amazonS3.doesBucketExistV2(bucketName));
List<Bucket> buckets = amazonS3.listBuckets();
assertEquals(2, buckets.size());
assertTrue(buckets.stream().anyMatch(
bucket -> bucket.getName().equals(bucketName)
));

//remove bucket
amazonS3.deleteBucket(bucketName);
assertFalse(amazonS3.doesBucketExistV2(bucketName));
buckets = amazonS3.listBuckets();
assertEquals(1, buckets.size());
assertFalse(buckets.stream().anyMatch(
bucket -> bucket.getName().equals(bucketName)
));
}
}

@Test
public void testS3Object() throws IOException {
Copy link
Member

Choose a reason for hiding this comment

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

perhaps we don't need both tests (or at least should make the container static + @ClassRule to avoid having to start it twice)

try (
CephContainer cephContainer = new CephContainer()
.withAwsAccessKey("test")
.withAwsSecretKey("test")
.withBucketName("test");
) {
cephContainer.start();

final AmazonS3 amazonS3 = buildS3Client(cephContainer);
String objectId = "test";
String testData = "This is test data";

//put object
amazonS3.putObject(cephContainer.getBucketName(), objectId, testData);
assertEquals(1, amazonS3.listObjects(cephContainer.getBucketName()).getObjectSummaries().size());
S3Object object = amazonS3.getObject(cephContainer.getBucketName(), objectId);
assertEquals(testData, IOUtils.toString(object.getObjectContent(), StandardCharsets.UTF_8));

//generate presigned url and download file
URL url = amazonS3.generatePresignedUrl(
cephContainer.getBucketName(),
objectId,
Date.from(Instant.now().plusSeconds(Duration.ofMinutes(5).toMillis())));

try (InputStream inputStream = url.openStream()) {
assertEquals(testData, IOUtils.toString(inputStream, StandardCharsets.UTF_8));
}
}
}

private AmazonS3 buildS3Client(final CephContainer cephContainer) {
// setting_up_s3_client {
final AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
.withCredentials(cephContainer.getAWSCredentialsProvider())
.withEndpointConfiguration(cephContainer.getAWSEndpointConfiguration())
.withPathStyleAccessEnabled(true)
.withClientConfiguration(
new ClientConfiguration()
.withProtocol(Protocol.HTTP)
.withSignerOverride("S3SignerType")
)
.build();
// }
return amazonS3;
}
}