diff --git a/docs/modules/databases/influxdb.md b/docs/modules/databases/influxdb.md index 8055f6aed57..afbf75c4985 100644 --- a/docs/modules/databases/influxdb.md +++ b/docs/modules/databases/influxdb.md @@ -4,10 +4,43 @@ Testcontainers module for InfluxData [InfluxDB](https://www.influxdata.com/produ ## Important note -There are breaking changes in InfluxDB 2.x. -For more information refer to the main [documentation](https://docs.influxdata.com/influxdb/v2.0/upgrade/v1-to-v2/). +There are breaking changes in InfluxDB 2.x. and InfluxDB 3.x. for more information refer to the main documentations: + +InfluxDB 3.x. [documentation](https://www.influxdata.com/blog/influxdb3-open-source-public-alpha/#heading2). + +InfluxDB 2.x. [documentation](https://docs.influxdata.com/influxdb/v2.0/upgrade/v1-to-v2/). + You can find more information about the official InfluxDB image on [Docker Hub](https://hub.docker.com/_/influxdb). +## InfluxDB 3.x usage example + +Running a `InfluxDBContainer` as a stand-in for InfluxDB in a test with default env variables: + + +[Create an InfluxDB container](../../../modules/influxdb/src/test/java/org/testcontainers/influxdb/InfluxDBV3ContainerTest.java) inside_block:createInfluxDBContainerV3WithAuthTokenTest + + + +The InfluxDB instance will be setup with the following data:
+ +| Property | Default Value | +|-------------|:-------------:| +| authDisable | false | + +For more details about the InfluxDB setup, please visit the official + +[InfluxDB documentation](https://docs.influxdata.com/influxdb3/core/get-started/) + +[InfluxDB config options](https://docs.influxdata.com/influxdb3/core/reference/config-options/) + +It is possible to overwrite the default property values. Create a container with disabled auth token: + +[Create an InfluxDB container with admin token](../../../modules/influxdb/src/test/java/org/testcontainers/influxdb/InfluxDBV3ContainerTest.java) inside_block:createInfluxDBContainerV3WithDisableAuthTokenTest + + +!!! hint +You can find the latest documentation about the InfluxDB 3.x Java client [here](https://github.com/InfluxCommunity/influxdb3-java). + ## InfluxDB 2.x usage example Running a `InfluxDBContainer` as a stand-in for InfluxDB in a test: diff --git a/modules/influxdb/build.gradle b/modules/influxdb/build.gradle index 3fcb2925e13..11a8a3d847c 100644 --- a/modules/influxdb/build.gradle +++ b/modules/influxdb/build.gradle @@ -11,6 +11,13 @@ dependencies { testImplementation 'org.assertj:assertj-core:3.27.4' testImplementation 'org.influxdb:influxdb-java:2.25' testImplementation "com.influxdb:influxdb-client-java:7.3.0" + testImplementation 'com.influxdb:influxdb3-java:1.2.0' +} + +test { + jvmArgs = [ + "--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED" + ] } tasks.japicmp { diff --git a/modules/influxdb/src/main/java/org/testcontainers/influxdb/InfluxDBContainer.java b/modules/influxdb/src/main/java/org/testcontainers/influxdb/InfluxDBContainer.java new file mode 100644 index 00000000000..df646f697ed --- /dev/null +++ b/modules/influxdb/src/main/java/org/testcontainers/influxdb/InfluxDBContainer.java @@ -0,0 +1,137 @@ +package org.testcontainers.influxdb; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.HttpResponseException; +import com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.classic.methods.HttpPost; +import com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.classic.HttpClients; +import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; + +/** + * Testcontainers implementation for InfluxDB 3 (InfluxDB IOx). + *

+ * Supported image: {@code influxdb} + *

+ * Exposed ports: 8181 + */ +public class InfluxDBContainer extends GenericContainer { + + /** + * The default port exposed by InfluxDB 3. + */ + private static final Integer INFLUXDB_PORT = 8181; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("influxdb"); + + /** + * The authentication token for InfluxDB 3. + */ + private String token; + + private boolean isAuthDisable; + + /** + * Creates a new InfluxDB 3 container using the specified Docker image. + * + * @param dockerImageName the name of the Docker image + */ + public InfluxDBContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + this.waitStrategy = + new HttpWaitStrategy() + .forPath("/health") + .forStatusCodeMatching(stausCode -> stausCode.equals(200) || stausCode.equals(401)); + + withCommand("influxdb3 serve --node-id local01 --object-store file --data-dir /home/influxdb3/.influxdb3"); + + addExposedPort(INFLUXDB_PORT); + } + + /** + * Creates an admin authentication token by making an HTTP request to the InfluxDB 3 instance. + * + * @return the generated authentication token + * @throws IllegalArgumentException if the token cannot be created due to HTTP or IO errors + * @throws HttpResponseException if the InfluxDB server returns a non-201 status code + */ + private String createToken() { + HttpPost httpPost = new HttpPost(format("%s/api/v3/configure/token/admin", getUrl())); + + httpPost.setHeader("Accept", "application/json"); + httpPost.setHeader("Content-Type", "application/json"); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + return httpClient.execute(httpPost, classicHttpResponse -> { + if (classicHttpResponse.getCode() != HttpStatus.SC_CREATED) { + throw new HttpResponseException( + classicHttpResponse.getCode(), + "Failed to get token" + ); + } + try (InputStream content = classicHttpResponse.getEntity().getContent()) { + return new ObjectMapper().readTree(content).get("token").asText(); + } + }); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot get token", e); + } + } + + /** + * Configures environment variables for the InfluxDB 3 container. + *

+ * This is automatically called by Testcontainers during container startup. + *

+ */ + @Override + protected void configure() { + addEnv("INFLUXDB3_START_WITHOUT_AUTH", Boolean.toString(isAuthDisable)); + } + + /** + * Disables authentication for this InfluxDB instance. + *

+ * When authentication is disabled, no token is required to access the database. + *

+ * + * @return this container instance for method chaining + */ + public InfluxDBContainer withAuthDisabled() { + this.isAuthDisable = true; + return this; + } + + /** + * Gets the URL for connecting to this InfluxDB instance. + *

+ * The URL includes the host and mapped port (since the actual port may change). + *

+ * + * @return the HTTP URL to access InfluxDB (e.g., "http://localhost:32768") + */ + public String getUrl() { + return "http://" + getHost() + ":" + getMappedPort(INFLUXDB_PORT); + } + + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + if (!isAuthDisable) { + this.token = createToken(); + } + } + + public String getToken() { + return token; + } +} diff --git a/modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBTestUtils.java b/modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBTestUtils.java index db9ae2bf79f..95c1b170736 100644 --- a/modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBTestUtils.java +++ b/modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBTestUtils.java @@ -7,4 +7,6 @@ public final class InfluxDBTestUtils { static final DockerImageName INFLUXDB_V1_TEST_IMAGE = DockerImageName.parse("influxdb:1.4.3"); static final DockerImageName INFLUXDB_V2_TEST_IMAGE = DockerImageName.parse("influxdb:2.0.7"); + + public static final DockerImageName INFLUXDB_V3_TEST_IMAGE = DockerImageName.parse("influxdb:3.5.0-core"); } diff --git a/modules/influxdb/src/test/java/org/testcontainers/influxdb/InfluxDBV3ContainerTest.java b/modules/influxdb/src/test/java/org/testcontainers/influxdb/InfluxDBV3ContainerTest.java new file mode 100644 index 00000000000..4ab9cb8e66a --- /dev/null +++ b/modules/influxdb/src/test/java/org/testcontainers/influxdb/InfluxDBV3ContainerTest.java @@ -0,0 +1,79 @@ +package org.testcontainers.influxdb; + +import com.influxdb.v3.client.InfluxDBClient; +import com.influxdb.v3.client.Point; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.InfluxDBTestUtils; + +import java.math.BigInteger; +import java.time.Instant; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class InfluxDBV3ContainerTest { + + @Test + public void createInfluxDBContainerV3WithAuthTokenTest() { + try (final InfluxDBContainer container = new InfluxDBContainer(InfluxDBTestUtils.INFLUXDB_V3_TEST_IMAGE)) { + container.start(); + try (final InfluxDBClient client = InfluxDBClient.getInstance(container.getUrl(), container.getToken().toCharArray(), "test")) { + assertThat(client).isNotNull(); + assertThat(client.getServerVersion()).isEqualTo("3.5.0"); + } catch (Exception e) { + fail("Cannot get instance of influxdb v3", e); + } + } + } + + @Test + public void createInfluxDBContainerV3WithDisableAuthTokenTest() { + try (final InfluxDBContainer container = new InfluxDBContainer(InfluxDBTestUtils.INFLUXDB_V3_TEST_IMAGE).withAuthDisabled()) { + container.start(); + try (final InfluxDBClient client = InfluxDBClient.getInstance(container.getUrl(), null, "test")) { + assertThat(client).isNotNull(); + assertThat(client.getServerVersion()).isEqualTo("3.5.0"); + } catch (Exception e) { + fail("Cannot get instance of influxdb v3", e); + } + } + } + + @Test + public void getTokenWithAuthDisableTest() { + try (final InfluxDBContainer container = new InfluxDBContainer(InfluxDBTestUtils.INFLUXDB_V3_TEST_IMAGE).withAuthDisabled()) { + container.start(); + assertThat(container.getToken()).isNull(); + } + } + + @Test + public void writeAndReadResultTest() { + try (final InfluxDBContainer container = new InfluxDBContainer(InfluxDBTestUtils.INFLUXDB_V3_TEST_IMAGE)) { + container.start(); + try (final InfluxDBClient client = InfluxDBClient.getInstance(container.getUrl(), container.getToken().toCharArray(), "test")) { + String location = "west"; + Double value = 55.15; + Point point = Point.measurement("temperature") + .setTag("location", location) + .setField("value", value) + .setTimestamp(Instant.now()); + client.writePoint(point); + String query = "select time,location,value from temperature"; + try (final Stream result = client.query(query)) { + List rows = result.collect(Collectors.toList()); + assertThat(rows).hasSize(1); + Object[] row = rows.get(0); + assertThat(row[0]).isNotNull().isInstanceOf(BigInteger.class); + assertThat((String) row[1]).isEqualTo(location); + assertThat((Double) row[2]).isEqualTo(value); + } + } catch (Exception e) { + fail("Cannot write or read data from influxdb v3", e); + } + } + } +}