Skip to content
Open
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
6 changes: 4 additions & 2 deletions modules/influxdb/testcontainers/influxdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
a container for an InfluxDB 1.x instance
- import the InfluxDb2Container class from the influxdb2/__init__.py module to spawn
a container for an InfluxDB 2.x instance
- import the InfluxDb3Container class from the influxdb3/__init__.py module to spawn
a container for an InfluxDB 3 Core instance

The 2 containers are separated in different modules for 2 reasons:
The 3 containers are separated in different modules for 3 reasons:
- because the Docker images are not designed to be used in the same way
- because the InfluxDB clients are different for 1.x and 2.x versions,
- because the InfluxDB clients are different for 1.x, 2.x and 3.x versions,
so you won't have to install dependencies that you do not need
"""

Expand Down
130 changes: 130 additions & 0 deletions modules/influxdb/testcontainers/influxdb3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from typing import TYPE_CHECKING, Optional, override

import requests

from testcontainers.core.container import DockerContainer
from testcontainers.core.wait_strategies import HttpWaitStrategy

if TYPE_CHECKING:
from influxdb_client_3 import InfluxDBClient3


class InfluxDb3Container(DockerContainer):
"""
Docker container for InfluxDB 3 Core.
Official Docker images for InfluxDB are hosted at https://hub.docker.com/_/influxdb/.

Example:

.. doctest::

>>> from testcontainers.influxdb3 import InfluxDb3Container

>>> with InfluxDb3Container() as influxdb3:
... url = influxdb3.get_url()
"""

INFLUXDB3_PORT = 8181

def __init__(
self,
image: str = "influxdb:3-core",
container_port: int = INFLUXDB3_PORT,
host_port: Optional[int] = None,
is_auth_disabled: bool = False,
**kw,
):
"""
Initialize InfluxDB 3 container.

Args:
image: Docker image to use. Defaults to ``influxdb:3-core``.
container_port: Port inside the container. Defaults to ``8181``.
host_port: Port on the host to bind to. If ``None``, a random port is assigned.
is_auth_disabled: If ``True``, disables authentication. Defaults to ``False``.
**kw: Additional keyword arguments passed to ``DockerContainer``.
"""
super().__init__(image=image, **kw)

self.container_port: int = container_port
self.host_port: Optional[int] = host_port
_ = self.with_bind_ports(self.container_port, self.host_port)

self._is_auth_disabled: bool = is_auth_disabled
self.token: Optional[str] = None

_ = self.with_command(
"influxdb3 serve --node-id local01 --object-store file --data-dir /home/influxdb3/.influxdb3"
)

if self._is_auth_disabled:
healthy_status_code = 200
_ = self.with_env("INFLUXDB3_START_WITHOUT_AUTH", str(self._is_auth_disabled).lower())
else:
healthy_status_code = 401

_ = self.waiting_for(HttpWaitStrategy(self.container_port, "/health").for_status_code(healthy_status_code))

def get_url(self) -> str:
"""
Get the URL to connect to the InfluxDB 3 instance.

Returns:
The HTTP URL of the running InfluxDB 3 container.
"""
host = self.get_container_host_ip()
port = self.get_exposed_port(self.container_port)
return f"http://{host}:{port}"

def _create_token(self) -> str:
url = f"{self.get_url()}/api/v3/configure/token/admin"
response = requests.post(
url, headers={"Accept": "application/json", "Content-Type": "application/json"}, timeout=10
)
response.raise_for_status()
return response.json()["token"]

def get_client(self, database: str, **kw) -> "InfluxDBClient3":
"""
Get an InfluxDB 3 client connected to this container.

Args:
database: Database name to connect to.
**kw: Additional keyword arguments passed to ``InfluxDBClient3``.

Returns:
An ``InfluxDBClient3`` instance connected to this container.
"""
from influxdb_client_3 import InfluxDBClient3

return InfluxDBClient3(
host=self.get_url(),
token=self.token,
database=database,
**kw,
)

@override
def start(self) -> "InfluxDb3Container":
"""
Start the InfluxDB 3 container and initialize authentication.

Returns:
The started ``InfluxDb3Container`` instance.
"""
_ = super().start()
if not self._is_auth_disabled:
self.token = self._create_token()
return self
28 changes: 26 additions & 2 deletions modules/influxdb/tests/test_influxdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
from testcontainers.influxdb import InfluxDbContainer
from testcontainers.influxdb1 import InfluxDb1Container
from testcontainers.influxdb2 import InfluxDb2Container
from testcontainers.influxdb3 import InfluxDb3Container


@mark.parametrize(
["image", "influxdb_container_class", "exposed_port"],
[
("influxdb:2.7", InfluxDb1Container, 8086),
("influxdb:1.8", InfluxDb2Container, 8086),
("influxdb:2.7", InfluxDb2Container, 8086),
("influxdb:1.8", InfluxDb1Container, 8086),
("influxdb:3-core", InfluxDb3Container, 8181),
],
)
def test_influxdbcontainer_get_url(image: str, influxdb_container_class: Type[InfluxDbContainer], exposed_port: int):
Expand Down Expand Up @@ -136,3 +138,25 @@ def test_influxdb2container_get_client():
assert len(results) == 2, "2 datapoints were retrieved"
assert results[0] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-11-30T09:30:00+00:00"), 0.42]
assert results[1] == ["influxdbcontainer", "ratio", datetime.fromisoformat("1978-12-25T10:30:00+00:00"), 0.55]


def test_influxdb3container_get_client():
"""
This is a test example showing how you could use testcontainers/influxdb for InfluxDB 3 Core versions with SQL queries
"""
with InfluxDb3Container("influxdb:3-core") as influxdb3_container:
client = influxdb3_container.get_client(database="testcontainers")

from influxdb_client_3 import Point

point = Point("influxdbcontainer").tag("source", "test").field("ratio", 0.42)
client.write(point)

table = client.query("SELECT source, ratio FROM influxdbcontainer", language="sql")
rows = table.to_pylist()

assert len(rows) == 1, "1 datapoint was retrieved"
assert rows[0]["source"] == "test"
assert rows[0]["ratio"] == 0.42

client.close()
68 changes: 68 additions & 0 deletions modules/influxdb/tests/test_influxdb3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from testcontainers.influxdb3 import InfluxDb3Container


def test_influxdb3_container_starts_with_auth():
with InfluxDb3Container("influxdb:3-core") as container:
token = container.token
assert token is not None, "Token should be generated when auth is enabled"
assert token.startswith("apiv3_"), "Token should start with apiv3_"


def test_influxdb3_container_starts_without_auth():
with InfluxDb3Container("influxdb:3-core", is_auth_disabled=True) as container:
token = container.token
assert token is None, "Token should be None when auth is disabled"


def test_influxdb3_container_get_url():
with InfluxDb3Container("influxdb:3-core", host_port=8181) as container:
url = container.get_url()
assert "8181" in url, "URL should contain the exposed port"
assert url.startswith("http://"), "URL should start with http://"


def test_influxdb3_write_and_query_data():
with InfluxDb3Container("influxdb:3-core") as container:
client = container.get_client(database="test1")

client.write("temperature,location=west value=55.15")

query = "SELECT time, location, value FROM temperature"
table = client.query(query=query, language="sql")
rows = table.to_pylist()

assert len(rows) == 1, "Should have retrieved 1 row"
assert rows[0]["location"] == "west"
assert rows[0]["value"] == 55.15

client.close()


def test_influxdb3_write_and_query_data_no_auth():
with InfluxDb3Container("influxdb:3-core", is_auth_disabled=True) as container:
client = container.get_client(database="test2")

client.write("humidity,location=east value=72.3")

query = "SELECT time, location, value FROM humidity"
table = client.query(query=query, language="sql")
rows = table.to_pylist()

assert len(rows) == 1, "Should have retrieved 1 row"
assert rows[0]["location"] == "east"
assert rows[0]["value"] == 72.3

client.close()
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ google = [
influxdb = [
"influxdb>=5",
"influxdb-client>=1",
"influxdb3-python>=0.1.0",
]
k3s = ["kubernetes", "pyyaml>=6.0.3"]
kafka = []
Expand Down
Loading