Skip to content

Commit

Permalink
fix(ryuk): Enable Ryuk test suite. Ryuk image 0.5.1 -> 0.7.0. Add RYU…
Browse files Browse the repository at this point in the history
…K_RECONNECTION_TIMEOUT env variable (#509)

- Re-enables the Ryuk test suite
- Bumps Ryuk container image from 0.5.1 - > 0.7.0
- Add env variable `RYUK_RECONNECTION_TIMEOUT` (As documented in the
[official Ryuk
repo](https://github.com/testcontainers/moby-ryuk?tab=readme-ov-file#ryuk-configuration))
  • Loading branch information
santi committed Mar 30, 2024
1 parent 90762e8 commit 472b2c2
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 30 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ The snippet above will spin up a postgres database in a container. The `get_conn

## Configuration

| Env Variable | Example | Description |
| ----------------------------------------- | ----------------------------- | ---------------------------------------- |
| `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk |
| `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container |
| `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk |
| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.5.1` | Custom image for ryuk |
| Env Variable | Example | Description |
| --------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------- |
| `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk |
| `TESTCONTAINERS_RYUK_PRIVILEGED` | `false` | Run ryuk as a privileged container |
| `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk |
| `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.7.0` | Custom image for ryuk |
| `RYUK_RECONNECTION_TIMEOUT` | `10s` | Reconnection timeout for Ryuk TCP socket before Ryuk reaps all dangling containers |
3 changes: 2 additions & 1 deletion core/testcontainers/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1))
TIMEOUT = MAX_TRIES * SLEEP_TIME

RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.5.1")
RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.7.0")
RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true"
RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true"
RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock")
RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s")
17 changes: 14 additions & 3 deletions core/testcontainers/core/container.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import contextlib
from platform import system
from socket import socket
from typing import TYPE_CHECKING, Optional

from testcontainers.core.config import RYUK_DISABLED, RYUK_DOCKER_SOCKET, RYUK_IMAGE, RYUK_PRIVILEGED
import docker.errors

from testcontainers.core.config import (
RYUK_DISABLED,
RYUK_DOCKER_SOCKET,
RYUK_IMAGE,
RYUK_PRIVILEGED,
RYUK_RECONNECTION_TIMEOUT,
)
from testcontainers.core.docker_client import DockerClient
from testcontainers.core.exceptions import ContainerStartException
from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID
Expand Down Expand Up @@ -177,8 +186,9 @@ def delete_instance(cls) -> None:
Reaper._socket.close()
Reaper._socket = None

if Reaper._container is not None:
Reaper._container.stop()
if Reaper._container is not None and Reaper._container._container is not None:
with contextlib.suppress(docker.errors.NotFound):
Reaper._container.stop()
Reaper._container = None

if Reaper._instance is not None:
Expand All @@ -194,6 +204,7 @@ def _create_instance(cls) -> "Reaper":
.with_exposed_ports(8080)
.with_volume_mapping(RYUK_DOCKER_SOCKET, "/var/run/docker.sock", "rw")
.with_kwargs(privileged=RYUK_PRIVILEGED, auto_remove=True)
.with_env("RYUK_RECONNECTION_TIMEOUT", RYUK_RECONNECTION_TIMEOUT)
.start()
)
wait_for_logs(Reaper._container, r".* Started!")
Expand Down
59 changes: 40 additions & 19 deletions core/tests/test_ryuk.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,58 @@
from contextlib import contextmanager

from time import sleep
import pytest
from pytest import MonkeyPatch

from docker import DockerClient
from docker.errors import NotFound

from testcontainers.core import container
from testcontainers.core import container as container_module
from testcontainers.core.container import Reaper
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_for_logs


@pytest.mark.skip("invalid test - ryuk logs 'Removed' right before exiting")
def test_wait_for_reaper():
def test_wait_for_reaper(monkeypatch: MonkeyPatch):
Reaper.delete_instance()
monkeypatch.setattr(container_module, "RYUK_RECONNECTION_TIMEOUT", "0.1s")
docker_client = DockerClient()
container = DockerContainer("hello-world").start()

container_id = container.get_wrapped_container().short_id
reaper_id = Reaper._container.get_wrapped_container().short_id

assert docker_client.containers.get(container_id) is not None
assert docker_client.containers.get(reaper_id) is not None

wait_for_logs(container, "Hello from Docker!")

assert Reaper._socket is not None
Reaper._socket.close()

assert Reaper._container is not None
wait_for_logs(Reaper._container, r".* Removed \d .*", timeout=30)
sleep(0.6) # Sleep until Ryuk reaps all dangling containers. 0.5 extra seconds for good measure.

with pytest.raises(NotFound):
docker_client.containers.get(container_id)
with pytest.raises(NotFound):
docker_client.containers.get(reaper_id)

# Cleanup Ryuk class fields after manual Ryuk shutdown
Reaper.delete_instance()


def test_container_without_ryuk(monkeypatch: MonkeyPatch):
Reaper.delete_instance()
monkeypatch.setattr(container_module, "RYUK_DISABLED", True)
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")
assert Reaper._instance is None


@contextmanager
def reset_reaper_instance():
old_value = Reaper._instance
Reaper._instance = None
yield
Reaper._instance = old_value
def test_ryuk_is_reused_in_same_process():
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")
reaper_instance = Reaper._instance

assert reaper_instance is not None

def test_container_without_ryuk(monkeypatch):
monkeypatch.setattr(container, "RYUK_DISABLED", True)
with reset_reaper_instance(), DockerContainer("hello-world") as cont:
wait_for_logs(cont, "Hello from Docker!")
assert Reaper._instance is None
with DockerContainer("hello-world") as container:
wait_for_logs(container, "Hello from Docker!")
assert reaper_instance is Reaper._instance
2 changes: 1 addition & 1 deletion index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Configuration
+-------------------------------------------+-------------------------------+------------------------------------------+
| ``TESTCONTAINERS_RYUK_DISABLED`` | ``false`` | Disable ryuk |
+-------------------------------------------+-------------------------------+------------------------------------------+
| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.5.1`` | Custom image for ryuk |
| ``RYUK_CONTAINER_IMAGE`` | ``testcontainers/ryuk:0.7.0`` | Custom image for ryuk |
+-------------------------------------------+-------------------------------+------------------------------------------+

Development and Contributing
Expand Down

0 comments on commit 472b2c2

Please sign in to comment.