Skip to content

Commit 3fa27ba

Browse files
author
Gudjon Ragnar Brynjarsson
committed
Add redpanda testcontainer module
1 parent 7358b49 commit 3fa27ba

File tree

4 files changed

+142
-0
lines changed

4 files changed

+142
-0
lines changed

modules/redpanda/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. autoclass:: testcontainers.redpanda.RedpandaContainer
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import tarfile
2+
import time
3+
from io import BytesIO
4+
from textwrap import dedent
5+
6+
from testcontainers.core.container import DockerContainer
7+
from testcontainers.core.waiting_utils import wait_for_logs
8+
9+
10+
class RedpandaContainer(DockerContainer):
11+
"""
12+
Redpanda container.
13+
14+
Example:
15+
16+
.. doctest::
17+
18+
>>> from testcontainers.redpanda import RedpandaContainer
19+
20+
>>> with RedpandaContainer() as redpanda:
21+
... connection = redpanda.get_bootstrap_server()
22+
"""
23+
24+
TC_START_SCRIPT = "/tc-start.sh"
25+
26+
def __init__(
27+
self,
28+
image: str = "docker.redpanda.com/redpandadata/redpanda:v23.1.13",
29+
**kwargs,
30+
) -> None:
31+
kwargs["entrypoint"] = "sh"
32+
super(RedpandaContainer, self).__init__(image, **kwargs)
33+
self.redpanda_port = 9092
34+
self.schema_registry_port = 8081
35+
self.with_exposed_ports(self.redpanda_port, self.schema_registry_port)
36+
37+
def get_bootstrap_server(self) -> str:
38+
host = self.get_container_host_ip()
39+
port = self.get_exposed_port(self.redpanda_port)
40+
return f"{host}:{port}"
41+
42+
def get_schema_registry_address(self) -> str:
43+
host = self.get_container_host_ip()
44+
port = self.get_exposed_port(self.schema_registry_port)
45+
return f"http://{host}:{port}"
46+
47+
def tc_start(self) -> None:
48+
host = self.get_container_host_ip()
49+
port = self.get_exposed_port(self.redpanda_port)
50+
51+
data = (
52+
dedent(
53+
f"""
54+
#!/bin/bash
55+
/usr/bin/rpk redpanda start --mode dev-container --smp 1 --memory 1G \
56+
--kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 \
57+
--advertise-kafka-addr PLAINTEXT://127.0.0.1:29092,OUTSIDE://{host}:{port}
58+
"""
59+
)
60+
.strip()
61+
.encode("utf-8")
62+
)
63+
64+
self.create_file(data, RedpandaContainer.TC_START_SCRIPT)
65+
66+
def start(self, timeout=10) -> "RedpandaContainer":
67+
script = RedpandaContainer.TC_START_SCRIPT
68+
command = f'-c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"'
69+
self.with_command(command)
70+
super().start()
71+
self.tc_start()
72+
wait_for_logs(self, r".*Started Kafka API server.*", timeout=timeout)
73+
return self
74+
75+
def create_file(self, content: bytes, path: str) -> None:
76+
with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar:
77+
tarinfo = tarfile.TarInfo(name=path)
78+
tarinfo.size = len(content)
79+
tarinfo.mtime = time.time()
80+
tar.addfile(tarinfo, BytesIO(content))
81+
archive.seek(0)
82+
self.get_wrapped_container().put_archive("/", archive)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import requests
2+
import json
3+
from kafka import KafkaConsumer, KafkaProducer, TopicPartition, KafkaAdminClient
4+
from kafka.admin import NewTopic
5+
from testcontainers.redpanda import RedpandaContainer
6+
7+
8+
def test_redpanda_producer_consumer():
9+
with RedpandaContainer() as container:
10+
produce_and_consume_message(container)
11+
12+
13+
def test_redpanda_confluent_latest():
14+
with RedpandaContainer(
15+
image="docker.redpanda.com/redpandadata/redpanda:latest"
16+
) as container:
17+
produce_and_consume_message(container)
18+
19+
20+
def test_schema_registry():
21+
with RedpandaContainer() as container:
22+
address = container.get_schema_registry_address()
23+
subject_name = "test-subject-value"
24+
url = f"{address}/subjects"
25+
26+
payload = {"schema": json.dumps({"type": "string"})}
27+
headers = {"Content-Type": "application/vnd.schemaregistry.v1+json"}
28+
create_result = requests.post(
29+
f"{url}/{subject_name}/versions", data=json.dumps(payload), headers=headers
30+
)
31+
assert create_result.status_code == 200
32+
33+
result = requests.get(url)
34+
assert result.status_code == 200
35+
assert subject_name in result.json()
36+
37+
38+
def produce_and_consume_message(container):
39+
topic = "test-topic"
40+
bootstrap_server = container.get_bootstrap_server()
41+
42+
admin = KafkaAdminClient(bootstrap_servers=[bootstrap_server])
43+
admin.create_topics([NewTopic(topic, 1, 1)])
44+
45+
producer = KafkaProducer(bootstrap_servers=[bootstrap_server])
46+
future = producer.send(topic, b"verification message")
47+
future.get(timeout=10)
48+
producer.close()
49+
50+
consumer = KafkaConsumer(bootstrap_servers=[bootstrap_server])
51+
tp = TopicPartition(topic, 0)
52+
consumer.assign([tp])
53+
consumer.seek_to_beginning()
54+
assert (
55+
consumer.end_offsets([tp])[tp] == 1
56+
), "Expected exactly one test message to be present on test topic !"

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ packages = [
4848
{ include = "testcontainers", from = "modules/postgres" },
4949
{ include = "testcontainers", from = "modules/rabbitmq" },
5050
{ include = "testcontainers", from = "modules/redis" },
51+
{ include = "testcontainers", from = "modules/redpanda" },
5152
{ include = "testcontainers", from = "modules/selenium" }
5253
]
5354

@@ -82,6 +83,7 @@ cx_Oracle = { version = "*", optional = true }
8283
psycopg2-binary = { version = "*", optional = true }
8384
pika = { version = "*", optional = true }
8485
redis = { version = "*", optional = true }
86+
redpanda = { version = "*", optional = true }
8587
selenium = { version = "*", optional = true }
8688

8789
[tool.poetry.extras]
@@ -105,6 +107,7 @@ oracle = ["sqlalchemy", "cx_Oracle"]
105107
postgres = ["sqlalchemy", "psycopg2-binary"]
106108
rabbitmq = ["pika"]
107109
redis = ["redis"]
110+
redpanda = []
108111
selenium = ["selenium"]
109112

110113
[tool.poetry.group.dev.dependencies]

0 commit comments

Comments
 (0)