338 lines
15 KiB
Python
338 lines
15 KiB
Python
import os
|
|
import time
|
|
|
|
import docker
|
|
import pytest
|
|
|
|
from functions import Functions
|
|
from processor import ProcessorInterface
|
|
|
|
|
|
def _get_hydrated_object(parsed_objects, lookup_key):
|
|
hydrated_object = {}
|
|
for key in parsed_objects:
|
|
for keys in parsed_objects[key]:
|
|
if lookup_key in keys:
|
|
hydrated_object[keys] = parsed_objects[key][keys]
|
|
return hydrated_object
|
|
|
|
|
|
def _get_ip_host(parsed_objects, lookup_key):
|
|
for key in parsed_objects:
|
|
for keys in parsed_objects[key]:
|
|
if lookup_key in keys:
|
|
return key
|
|
|
|
|
|
def test_processor_docker():
|
|
try:
|
|
client = docker.from_env()
|
|
except docker.errors.DockerException:
|
|
pytest.skip("There is no docker environment")
|
|
|
|
if len(client.containers.list()) > 0:
|
|
pytest.skip("I cannot run this test with other containers running.")
|
|
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
labels={
|
|
"easyhaproxy.http.port": "80",
|
|
"easyhaproxy.http.localport": "8080",
|
|
"easyhaproxy.http.host": "host1.local",
|
|
|
|
"easyhaproxy.http2.port": "90",
|
|
"easyhaproxy.http2.localport": "9000",
|
|
"easyhaproxy.http2.host": "host2.local",
|
|
"easyhaproxy.http2.certbot": "true",
|
|
})
|
|
container2 = client.containers.run("byjg/static-httpserver",
|
|
name="test2_processor_docker",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
labels={
|
|
"easyhaproxy.ssl.port": "443",
|
|
"easyhaproxy.ssl.localport": "8080",
|
|
"easyhaproxy.ssl.host": "hostssl.local",
|
|
"easyhaproxy.ssl.sslcert": "U29tZSBQRU0gQ2VydGlmaWNhdGU="
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
os.environ['EASYHAPROXY_CERTBOT_EMAIL'] = 'docker@example.org'
|
|
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
assert static.get_certbot_hosts() is None
|
|
|
|
assert {
|
|
'easyhaproxy.http.host': 'host1.local',
|
|
'easyhaproxy.http.localport': '8080',
|
|
'easyhaproxy.http.port': '80',
|
|
'easyhaproxy.http2.host': 'host2.local',
|
|
'easyhaproxy.http2.localport': '9000',
|
|
'easyhaproxy.http2.port': '90',
|
|
'easyhaproxy.http2.certbot': 'true',
|
|
} == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.http")
|
|
assert {
|
|
'easyhaproxy.ssl.host': 'hostssl.local',
|
|
'easyhaproxy.ssl.localport': '8080',
|
|
'easyhaproxy.ssl.port': '443',
|
|
'easyhaproxy.ssl.sslcert': 'U29tZSBQRU0gQ2VydGlmaWNhdGU='
|
|
} == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.ssl.")
|
|
|
|
assert static.get_hosts() is None
|
|
assert static.get_certs() == {}
|
|
|
|
haproxy_cfg = static.get_haproxy_conf()
|
|
assert haproxy_cfg == Functions.load(os.path.join(os.path.dirname(os.path.realpath(__file__)), "./expected/docker.txt")).replace("test_processor_docker", _get_ip_host(
|
|
static.get_parsed_object(), "easyhaproxy.http")).replace("test2_processor_docker", _get_ip_host(static.get_parsed_object(), "easyhaproxy.ssl"))
|
|
|
|
assert static.get_certbot_hosts() == ['host2.local']
|
|
assert static.get_hosts() == [
|
|
'hostssl.local:443',
|
|
'host1.local:80',
|
|
'host2.local:90'
|
|
]
|
|
assert static.get_certs() == {
|
|
'hostssl.local.pem': 'Some PEM Certificate'
|
|
}
|
|
finally:
|
|
del os.environ['EASYHAPROXY_CERTBOT_EMAIL']
|
|
container.stop()
|
|
container2.stop()
|
|
|
|
|
|
def _skip_unless_docker_is_idle():
|
|
try:
|
|
client = docker.from_env()
|
|
except docker.errors.DockerException:
|
|
pytest.skip("There is no docker environment")
|
|
|
|
if len(client.containers.list()) > 0:
|
|
pytest.skip("I cannot run this test with other containers running.")
|
|
|
|
return client
|
|
|
|
|
|
def _bridge_gateway(client):
|
|
return client.networks.get("bridge").attrs["IPAM"]["Config"][0]["Gateway"]
|
|
|
|
|
|
def test_processor_docker_host_network():
|
|
client = _skip_unless_docker_is_idle()
|
|
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_host",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
network_mode="host",
|
|
labels={
|
|
"easyhaproxy.hostmode.port": "80",
|
|
"easyhaproxy.hostmode.localport": "8080",
|
|
"easyhaproxy.hostmode.host": "hostmode.local",
|
|
})
|
|
container2 = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_bridge",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
labels={
|
|
"easyhaproxy.bridged.port": "80",
|
|
"easyhaproxy.bridged.localport": "8080",
|
|
"easyhaproxy.bridged.host": "bridged.local",
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
|
|
assert {
|
|
'easyhaproxy.hostmode.host': 'hostmode.local',
|
|
'easyhaproxy.hostmode.localport': '8080',
|
|
'easyhaproxy.hostmode.port': '80',
|
|
} == _get_hydrated_object(static.get_parsed_object(), "easyhaproxy.hostmode")
|
|
|
|
# The container shares the host network namespace, so it is served through the gateway.
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == _bridge_gateway(client)
|
|
|
|
# The container on the bridge network keeps being served through its own address.
|
|
bridged_ip = client.containers.get(container2.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.bridged") == bridged_ip
|
|
|
|
static.get_haproxy_conf()
|
|
assert static.get_hosts() == [
|
|
'bridged.local:80',
|
|
'hostmode.local:80'
|
|
]
|
|
finally:
|
|
container.stop()
|
|
container2.stop()
|
|
|
|
|
|
def test_processor_docker_host_network_merges_labels():
|
|
client = _skip_unless_docker_is_idle()
|
|
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_host1",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
network_mode="host",
|
|
labels={
|
|
"easyhaproxy.first.port": "80",
|
|
"easyhaproxy.first.localport": "8080",
|
|
"easyhaproxy.first.host": "first.local",
|
|
})
|
|
container2 = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_host2",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
network_mode="host",
|
|
environment={"PORT": "9000", "TLS_PORT": "9443"},
|
|
labels={
|
|
"easyhaproxy.second.port": "80",
|
|
"easyhaproxy.second.localport": "9000",
|
|
"easyhaproxy.second.host": "second.local",
|
|
})
|
|
container3 = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_bridge_merge",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
labels={
|
|
"easyhaproxy.bridged.port": "80",
|
|
"easyhaproxy.bridged.localport": "8080",
|
|
"easyhaproxy.bridged.host": "bridged.local",
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
gateway = _bridge_gateway(client)
|
|
|
|
# Both containers resolve to the same address, so their labels are merged, not replaced.
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.first") == gateway
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.second") == gateway
|
|
merged = {key: value for key, value in static.get_parsed_object()[gateway].items()
|
|
if key.startswith("easyhaproxy.")}
|
|
assert {
|
|
'easyhaproxy.first.host': 'first.local',
|
|
'easyhaproxy.first.localport': '8080',
|
|
'easyhaproxy.first.port': '80',
|
|
'easyhaproxy.second.host': 'second.local',
|
|
'easyhaproxy.second.localport': '9000',
|
|
'easyhaproxy.second.port': '80',
|
|
} == merged
|
|
|
|
haproxy_cfg = static.get_haproxy_conf()
|
|
assert f"server srv-0 {gateway}:8080" in haproxy_cfg
|
|
assert f"server srv-0 {gateway}:9000" in haproxy_cfg
|
|
finally:
|
|
container.stop()
|
|
container2.stop()
|
|
container3.stop()
|
|
|
|
|
|
def test_processor_docker_host_network_ip_override():
|
|
client = _skip_unless_docker_is_idle()
|
|
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_host_override",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
network_mode="host",
|
|
labels={
|
|
"easyhaproxy.hostmode.port": "80",
|
|
"easyhaproxy.hostmode.localport": "8080",
|
|
"easyhaproxy.hostmode.host": "hostmode.local",
|
|
})
|
|
container2 = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_bridge_override",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
labels={
|
|
"easyhaproxy.bridged.port": "80",
|
|
"easyhaproxy.bridged.localport": "8080",
|
|
"easyhaproxy.bridged.host": "bridged.local",
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
os.environ['EASYHAPROXY_HOST_NETWORK_IP'] = '10.20.30.40'
|
|
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.hostmode") == '10.20.30.40'
|
|
finally:
|
|
del os.environ['EASYHAPROXY_HOST_NETWORK_IP']
|
|
container.stop()
|
|
container2.stop()
|
|
|
|
|
|
def test_processor_docker_shared_namespace():
|
|
client = _skip_unless_docker_is_idle()
|
|
|
|
owner = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_owner",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True)
|
|
time.sleep(1)
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_sidecar",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
environment={"PORT": "9000", "TLS_PORT": "9443"},
|
|
network_mode="container:test_processor_docker_owner",
|
|
labels={
|
|
"easyhaproxy.sidecar.port": "80",
|
|
"easyhaproxy.sidecar.localport": "9000",
|
|
"easyhaproxy.sidecar.host": "sidecar.local",
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
|
|
# The sidecar is reachable at the address of the container owning the network namespace.
|
|
owner_ip = client.containers.get(owner.name).attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
|
|
assert _get_ip_host(static.get_parsed_object(), "easyhaproxy.sidecar") == owner_ip
|
|
finally:
|
|
container.stop()
|
|
owner.stop()
|
|
|
|
|
|
def test_processor_docker_host_network_only():
|
|
client = _skip_unless_docker_is_idle()
|
|
|
|
container = client.containers.run("byjg/static-httpserver",
|
|
name="test_processor_docker_host_only",
|
|
detach=True,
|
|
auto_remove=True,
|
|
remove=True,
|
|
network_mode="host",
|
|
labels={
|
|
"easyhaproxy.hostmode.port": "80",
|
|
"easyhaproxy.hostmode.localport": "8080",
|
|
"easyhaproxy.hostmode.host": "hostmode.local",
|
|
})
|
|
try:
|
|
time.sleep(1)
|
|
|
|
# There is no network to borrow, but the discovery must not fail.
|
|
static = ProcessorInterface.factory(ProcessorInterface.DOCKER)
|
|
assert static.get_parsed_object() == {}
|
|
assert static.get_haproxy_conf() != ""
|
|
finally:
|
|
container.stop()
|
|
|
|
|
|
# test_processor_docker()
|