-
Notifications
You must be signed in to change notification settings - Fork 3k
Test server listening on IPv4/IPv6 #2255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c691f23
2185322
07d5526
45c143b
329bf69
06a4a0a
68d3d65
d864483
80793bf
e45b9c6
3a07140
dc6c26b
d03069a
fba8fec
e618807
ae3e529
0213080
f7d32e1
db9a25e
99f99e3
e165880
e0b5a54
8df7eab
ac3dbd5
1a62fa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| #!/usr/bin/env python | ||
| # Copyright (c) Jupyter Development Team. | ||
| # Distributed under the terms of the Modified BSD License. | ||
| import socket | ||
| import time | ||
|
|
||
| import requests | ||
|
|
||
|
|
||
| def make_get_request() -> None: | ||
| # Give some time for server to start | ||
| finish_time = time.time() + 10 | ||
| sleep_time = 1 | ||
| while time.time() < finish_time: | ||
| time.sleep(sleep_time) | ||
| try: | ||
| resp = requests.get("http://localhost:8888/api") | ||
| resp.raise_for_status() | ||
| except requests.RequestException: | ||
| pass | ||
| resp.raise_for_status() | ||
|
|
||
|
|
||
| def check_addrs(family: socket.AddressFamily) -> None: | ||
| assert family in {socket.AF_INET, socket.AF_INET6} | ||
|
|
||
| # https://docs.python.org/3/library/socket.html#socket.getaddrinfo | ||
| addrs = { | ||
| s[4][0] | ||
| for s in socket.getaddrinfo(host=socket.gethostname(), port=None, family=family) | ||
| } | ||
| loopback_addr = "127.0.0.1" if family == socket.AF_INET else "::1" | ||
| addrs.discard(loopback_addr) | ||
|
|
||
| assert addrs, f"No external addresses found for family: {family}" | ||
|
|
||
| for addr in addrs: | ||
| url = ( | ||
| f"http://{addr}:8888/api" | ||
| if family == socket.AF_INET | ||
| else f"http://[{addr}]:8888/api" | ||
| ) | ||
| r = requests.get(url) | ||
| r.raise_for_status() | ||
| assert "version" in r.json() | ||
| print(f"Successfully connected to: {url}") | ||
|
|
||
|
|
||
| def test_connect() -> None: | ||
| make_get_request() | ||
|
|
||
| check_addrs(socket.AF_INET) | ||
| check_addrs(socket.AF_INET6) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| test_connect() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # Copyright (c) Jupyter Development Team. | ||
| # Distributed under the terms of the Modified BSD License. | ||
| import logging | ||
| from collections.abc import Generator | ||
| from pathlib import Path | ||
| from random import randint | ||
|
|
||
| import docker | ||
| import pytest # type: ignore | ||
|
|
||
| from tests.utils.tracked_container import TrackedContainer | ||
|
|
||
| LOGGER = logging.getLogger(__name__) | ||
| THIS_DIR = Path(__file__).parent.resolve() | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def ipv6_network(docker_client: docker.DockerClient) -> Generator[str, None, None]: | ||
| """Create a dual-stack IPv6 docker network""" | ||
| # Doesn't have to be routable since we're testing inside the container | ||
| subnet64 = "fc00:" + ":".join(hex(randint(0, 2**16))[2:] for _ in range(3)) | ||
| name = subnet64.replace(":", "-") | ||
| docker_client.networks.create( | ||
| name, | ||
| ipam=docker.types.IPAMPool( | ||
| subnet=subnet64 + "::/64", | ||
| gateway=subnet64 + "::1", | ||
| ), | ||
| enable_ipv6=True, | ||
| internal=True, | ||
| ) | ||
| yield name | ||
| docker_client.networks.get(name).remove() | ||
|
|
||
|
|
||
| def test_ipv46(container: TrackedContainer, ipv6_network: str) -> None: | ||
| """Check server is listening on the expected IP families""" | ||
| host_data_dir = THIS_DIR / "data" | ||
| cont_data_dir = "/home/jovyan/data" | ||
| LOGGER.info("Testing that server is listening on IPv4 and IPv6 ...") | ||
| running_container = container.run_detached( | ||
| network=ipv6_network, | ||
| volumes={str(host_data_dir): {"bind": cont_data_dir, "mode": "ro,z"}}, | ||
| tty=True, | ||
| ) | ||
|
|
||
| command = ["python", f"{cont_data_dir}/check_listening.py"] | ||
| exec_result = running_container.exec_run(command) | ||
| LOGGER.info(exec_result.output.decode()) | ||
| assert exec_result.exit_code == 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ | |
| from docker.models.containers import Container | ||
|
|
||
|
|
||
| def get_health(container: Container) -> str: | ||
| api_client = docker.APIClient() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is funny coincidence. Didn't notice this bug. That's why it was impossible to make both healthcheck and ipv6 tests work. |
||
| inspect_results = api_client.inspect_container(container.name) | ||
| def get_health(container: Container, client: docker.DockerClient) -> str: | ||
| inspect_results = client.api.inspect_container(container.name) | ||
| return inspect_results["State"]["Health"]["Status"] # type: ignore | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This magic option changes anything - using it there is need to change other code around docker client creation, it will automatically use the latest docker client.
It is also more general, as it will also work for other places where we're creating docker client - in tagging, for example.