mirror of
https://github.com/thib8956/nginx-proxy
synced 2024-11-24 21:06:31 +00:00
TESTS: add tests for IPv6
This commit is contained in:
parent
e6b9d2f5e7
commit
9f26efdf86
@ -88,6 +88,14 @@ The `nginxproxy` fixture will provide you with a replacement for the python [req
|
||||
|
||||
Also this requests replacement is preconfigured to use the Certificate Authority root certificate [certs/ca-root.crt](certs/) to validate https connections.
|
||||
|
||||
Furthermore, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not supported by the system or docker, that particular test will be skipped.
|
||||
|
||||
def test_forwards_to_web1_ipv6(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True)
|
||||
assert r.status_code == 200
|
||||
assert r.text == "answer from port 81\n"
|
||||
|
||||
|
||||
|
||||
### The web docker image
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from __future__ import print_function
|
||||
import json
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
@ -12,14 +12,18 @@ import backoff
|
||||
import docker
|
||||
import pytest
|
||||
import requests
|
||||
from _pytest._code.code import ReprExceptionInfo
|
||||
from requests.packages.urllib3.util.connection import HAS_IPV6
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.getLogger('backoff').setLevel(logging.INFO)
|
||||
logging.getLogger('DNS').setLevel(logging.INFO)
|
||||
logging.getLogger('DNS').setLevel(logging.DEBUG)
|
||||
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
|
||||
|
||||
CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
|
||||
I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER = os.path.isfile("/.dockerenv")
|
||||
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
|
||||
|
||||
|
||||
docker_client = docker.from_env()
|
||||
|
||||
@ -30,6 +34,24 @@ docker_client = docker.from_env()
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ipv6(force_ipv6=True):
|
||||
"""
|
||||
Meant to be used as a context manager to force IPv6 sockets:
|
||||
|
||||
with ipv6():
|
||||
nginxproxy.get("http://something.nginx-proxy.local") # force use of IPv6
|
||||
|
||||
with ipv6(False):
|
||||
nginxproxy.get("http://something.nginx-proxy.local") # legacy behavior
|
||||
|
||||
|
||||
"""
|
||||
global FORCE_CONTAINER_IPV6
|
||||
FORCE_CONTAINER_IPV6 = force_ipv6
|
||||
yield
|
||||
FORCE_CONTAINER_IPV6 = False
|
||||
|
||||
|
||||
class requests_for_docker(object):
|
||||
"""
|
||||
@ -54,40 +76,46 @@ class requests_for_docker(object):
|
||||
return get_nginx_conf_from_container(nginx_proxy_containers[0])
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _get(*args, **kwargs):
|
||||
return self.session.get(*args, **kwargs)
|
||||
return _get(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _get(*args, **kwargs):
|
||||
return self.session.get(*args, **kwargs)
|
||||
return _get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _post(*args, **kwargs):
|
||||
return self.session.post(*args, **kwargs)
|
||||
return _post(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _post(*args, **kwargs):
|
||||
return self.session.post(*args, **kwargs)
|
||||
return _post(*args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _put(*args, **kwargs):
|
||||
return self.session.put(*args, **kwargs)
|
||||
return _put(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _put(*args, **kwargs):
|
||||
return self.session.put(*args, **kwargs)
|
||||
return _put(*args, **kwargs)
|
||||
|
||||
def head(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _head(*args, **kwargs):
|
||||
return self.session.head(*args, **kwargs)
|
||||
return _head(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _head(*args, **kwargs):
|
||||
return self.session.head(*args, **kwargs)
|
||||
return _head(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _delete(*args, **kwargs):
|
||||
return self.session.delete(*args, **kwargs)
|
||||
return _delete(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _delete(*args, **kwargs):
|
||||
return self.session.delete(*args, **kwargs)
|
||||
return _delete(*args, **kwargs)
|
||||
|
||||
def options(self, *args, **kwargs):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _options(*args, **kwargs):
|
||||
return self.session.options(*args, **kwargs)
|
||||
return _options(*args, **kwargs)
|
||||
with ipv6(kwargs.pop('ipv6', False)):
|
||||
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
|
||||
def _options(*args, **kwargs):
|
||||
return self.session.options(*args, **kwargs)
|
||||
return _options(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(requests, name)
|
||||
@ -95,20 +123,45 @@ class requests_for_docker(object):
|
||||
|
||||
def container_ip(container):
|
||||
"""
|
||||
return the IP address of a container
|
||||
return the IP address of a container.
|
||||
|
||||
If the global FORCE_CONTAINER_IPV6 flag is set, return the IPv6 address
|
||||
"""
|
||||
global FORCE_CONTAINER_IPV6
|
||||
if FORCE_CONTAINER_IPV6:
|
||||
if not HAS_IPV6:
|
||||
pytest.skip("This system does not support IPv6")
|
||||
ip = container_ipv6(container)
|
||||
if ip == '':
|
||||
pytest.skip("Container %s has no IPv6 address" % container.name)
|
||||
else:
|
||||
return ip
|
||||
else:
|
||||
net_info = container.attrs["NetworkSettings"]["Networks"]
|
||||
if "bridge" in net_info:
|
||||
return net_info["bridge"]["IPAddress"]
|
||||
|
||||
# not default bridge network, fallback on first network defined
|
||||
network_name = net_info.keys()[0]
|
||||
return net_info[network_name]["IPAddress"]
|
||||
|
||||
|
||||
def container_ipv6(container):
|
||||
"""
|
||||
return the IPv6 address of a container.
|
||||
"""
|
||||
net_info = container.attrs["NetworkSettings"]["Networks"]
|
||||
if "bridge" in net_info:
|
||||
return net_info["bridge"]["IPAddress"]
|
||||
return net_info["bridge"]["GlobalIPv6Address"]
|
||||
|
||||
# not default bridge network, fallback on first network defined
|
||||
network_name = net_info.keys()[0]
|
||||
return net_info[network_name]["IPAddress"]
|
||||
return net_info[network_name]["GlobalIPv6Address"]
|
||||
|
||||
|
||||
def nginx_proxy_dns_resolver(domain_name):
|
||||
"""
|
||||
if "nginx-proxy" if found in domain_name, return the ip address of the docker container
|
||||
if "nginx-proxy" if found in host, return the ip address of the docker container
|
||||
issued from the docker image jwilder/nginx-proxy:test.
|
||||
|
||||
:return: IP or None
|
||||
@ -164,24 +217,21 @@ def monkey_patch_urllib_dns_resolver():
|
||||
dns_cache = {}
|
||||
def new_getaddrinfo(*args):
|
||||
logging.getLogger('DNS').debug("resolving domain name %s" % repr(args))
|
||||
_args = list(args)
|
||||
|
||||
# custom DNS resolvers
|
||||
ip = nginx_proxy_dns_resolver(args[0])
|
||||
if ip is None:
|
||||
ip = docker_container_dns_resolver(args[0])
|
||||
if ip is not None:
|
||||
return [
|
||||
(socket.AF_INET, socket.SOCK_STREAM, 6, '', (ip, args[1])),
|
||||
(socket.AF_INET, socket.SOCK_DGRAM, 17, '', (ip, args[1])),
|
||||
(socket.AF_INET, socket.SOCK_RAW, 0, '', (ip, args[1]))
|
||||
]
|
||||
_args[0] = ip
|
||||
|
||||
# fallback on original DNS resolver
|
||||
# call on original DNS resolver, with eventually the original host changed to the wanted IP address
|
||||
try:
|
||||
return dns_cache[args]
|
||||
return dns_cache[tuple(_args)]
|
||||
except KeyError:
|
||||
res = prv_getaddrinfo(*args)
|
||||
dns_cache[args] = res
|
||||
res = prv_getaddrinfo(*_args)
|
||||
dns_cache[tuple(_args)] = res
|
||||
return res
|
||||
socket.getaddrinfo = new_getaddrinfo
|
||||
return prv_getaddrinfo
|
||||
@ -370,7 +420,11 @@ def nginxproxy():
|
||||
r = nginxproxy.get("http://foo.com")
|
||||
|
||||
The difference is that in case an HTTP requests has status code 404 or 502 (which mostly
|
||||
indicates that nginx has just reloaded), we retry up to 30 times the query
|
||||
indicates that nginx has just reloaded), we retry up to 30 times the query.
|
||||
|
||||
Also, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests
|
||||
made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not
|
||||
supported by the system or docker, that particular test will be skipped.
|
||||
"""
|
||||
yield requests_for_docker()
|
||||
|
||||
@ -384,10 +438,11 @@ def nginxproxy():
|
||||
# pytest hook to display additionnal stuff in test report
|
||||
def pytest_runtest_logreport(report):
|
||||
if report.failed:
|
||||
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "jwilder/nginx-proxy:test"})
|
||||
for container in test_containers:
|
||||
report.longrepr.addsection('nginx-proxy logs', container.logs())
|
||||
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
|
||||
if isinstance(report.longrepr, ReprExceptionInfo):
|
||||
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "jwilder/nginx-proxy:test"})
|
||||
for container in test_containers:
|
||||
report.longrepr.addsection('nginx-proxy logs', container.logs())
|
||||
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
|
||||
|
||||
|
||||
|
||||
|
@ -1,15 +1,35 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_unknown_virtual_host(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://nginx-proxy/port")
|
||||
assert r.status_code == 503
|
||||
|
||||
|
||||
def test_forwards_to_web1(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://web1.nginx-proxy.tld/port")
|
||||
assert r.status_code == 200
|
||||
assert r.text == "answer from port 81\n"
|
||||
|
||||
|
||||
def test_forwards_to_web2(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://web2.nginx-proxy.tld/port")
|
||||
assert r.status_code == 200
|
||||
assert r.text == "answer from port 82\n"
|
||||
|
||||
|
||||
def test_unknown_virtual_host_ipv6(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://nginx-proxy/port", ipv6=True)
|
||||
assert r.status_code == 503
|
||||
|
||||
|
||||
def test_forwards_to_web1_ipv6(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True)
|
||||
assert r.status_code == 200
|
||||
assert r.text == "answer from port 81\n"
|
||||
|
||||
|
||||
def test_forwards_to_web2_ipv6(docker_compose, nginxproxy):
|
||||
r = nginxproxy.get("http://web2.nginx-proxy.tld/port", ipv6=True)
|
||||
assert r.status_code == 200
|
||||
assert r.text == "answer from port 82\n"
|
||||
|
@ -8,7 +8,5 @@ def test_http_is_forwarded(docker_compose, nginxproxy):
|
||||
|
||||
|
||||
def test_https_is_disabled(docker_compose, nginxproxy):
|
||||
with pytest.raises(ConnectionError) as excinfo:
|
||||
r = nginxproxy.get("https://web.nginx-proxy.tld/", allow_redirects=False)
|
||||
|
||||
assert "[Errno 93] Protocol not supported" in str(excinfo.value)
|
||||
with pytest.raises(ConnectionError):
|
||||
nginxproxy.get("https://web.nginx-proxy.tld/", allow_redirects=False)
|
||||
|
Loading…
Reference in New Issue
Block a user