Skip to content

Commit

Permalink
added resolver directive (#66)
Browse files Browse the repository at this point in the history
* added resolver directive

* tests and tls path

---------

Co-authored-by: Michael Dmitry <[email protected]>
  • Loading branch information
PietroPasotti and michaeldmitry authored Nov 15, 2024
1 parent e2b5ab7 commit 6d15deb
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 9 deletions.
20 changes: 19 additions & 1 deletion src/nginx_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""Nginx workload."""

import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, cast

import crossplane
Expand All @@ -18,13 +19,15 @@
from tempo_config import TempoRole

logger = logging.getLogger(__name__)
RESOLV_CONF_PATH = "/etc/resolv.conf"


class NginxConfig:
"""Helper class to manage the nginx workload."""

def __init__(self, server_name: str):
self.server_name = server_name
self.dns_IP_address = _get_dns_ip_address()

def config(self, coordinator: Coordinator) -> str:
"""Build and return the Nginx configuration."""
Expand Down Expand Up @@ -163,7 +166,10 @@ def _locations(self, upstream: str, grpc: bool, tls: bool) -> List[Dict[str, Any
]
return nginx_locations

def _resolver(self, custom_resolver: Optional[List[Any]] = None) -> List[Dict[str, Any]]:
def _resolver(
self,
custom_resolver: Optional[str] = None,
) -> List[Dict[str, Any]]:
if custom_resolver:
return [{"directive": "resolver", "args": [custom_resolver]}]
return [{"directive": "resolver", "args": ["kube-dns.kube-system.svc.cluster.local."]}]
Expand Down Expand Up @@ -246,6 +252,7 @@ def _build_server_config(
{"directive": "ssl_certificate_key", "args": [KEY_PATH]},
{"directive": "ssl_protocols", "args": ["TLSv1", "TLSv1.1", "TLSv1.2"]},
{"directive": "ssl_ciphers", "args": ["HIGH:!aNULL:!MD5"]}, # codespell:ignore
*self._resolver(custom_resolver=self.dns_IP_address),
*self._locations(upstream, grpc, tls),
],
}
Expand All @@ -261,6 +268,7 @@ def _build_server_config(
"args": ["X-Scope-OrgID", "$ensured_x_scope_orgid"],
},
{"directive": "server_name", "args": [self.server_name]},
*self._resolver(custom_resolver=self.dns_IP_address),
*self._locations(upstream, grpc, tls),
],
}
Expand All @@ -276,3 +284,13 @@ def _is_protocol_grpc(self, protocol: str) -> bool:
):
return True
return False


def _get_dns_ip_address():
"""Obtain DNS ip address from /etc/resolv.conf."""
resolv = Path(RESOLV_CONF_PATH).read_text()
for line in resolv.splitlines():
if line.startswith("nameserver"):
# assume there's only one
return line.split()[1].strip()
raise RuntimeError("cannot find nameserver in /etc/resolv.conf")
56 changes: 48 additions & 8 deletions tests/scenario/test_nginx.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import logging
import tempfile
from contextlib import contextmanager
from pathlib import Path
from typing import List
from unittest.mock import patch

import pytest

from nginx_config import NginxConfig
from nginx_config import NginxConfig, _get_dns_ip_address
from tempo import Tempo

logger = logging.getLogger(__name__)
sample_dns_ip = "198.18.0.0"


def test_nginx_config_is_list_before_crossplane(context, nginx_container, coordinator):
Expand Down Expand Up @@ -89,34 +94,69 @@ def test_nginx_config_is_parsed_with_workers(context, nginx_container, coordinat
},
),
)
@pytest.mark.parametrize("tls", (True, False))
def test_nginx_config_contains_upstreams_and_proxy_pass(
context, nginx_container, coordinator, addresses
context, nginx_container, coordinator, addresses, tls
):
coordinator.cluster.gather_addresses_by_role.return_value = addresses
coordinator.nginx.are_certificates_on_disk = tls

nginx = NginxConfig("localhost")
with mock_resolv_conf(f"nameserver {sample_dns_ip}"):
nginx = NginxConfig("localhost")

prepared_config = nginx.config(coordinator)

for role, addresses in addresses.items():
for address in addresses:
if role == "distributor":
_assert_config_per_role(Tempo.receiver_ports, address, prepared_config)
_assert_config_per_role(Tempo.receiver_ports, address, prepared_config, tls)
if role == "query-frontend":
_assert_config_per_role(Tempo.server_ports, address, prepared_config)
_assert_config_per_role(Tempo.server_ports, address, prepared_config, tls)


def _assert_config_per_role(source_dict, address, prepared_config):
def _assert_config_per_role(source_dict, address, prepared_config, tls):
# as entire config is in a format that's hard to parse (and crossplane returns a string), we look for servers,
# upstreams and correct proxy/grpc_pass instructions.
for port in source_dict.values():
assert f"server {address}:{port};" in prepared_config
assert f"listen {port}" in prepared_config
assert f"listen [::]:{port}" in prepared_config

for protocol in source_dict.keys():
sanitised_protocol = protocol.replace("_", "-")
assert f"upstream {sanitised_protocol}" in prepared_config

# kind of a weak test: it should be in all server blocks, we only check it is found at all.
assert f"resolver {sample_dns_ip};" in prepared_config

if "grpc" in protocol:
assert f"grpc_pass grpcs://{sanitised_protocol}" in prepared_config
assert f"grpc_pass grpc{'s' if tls else ''}://{sanitised_protocol}" in prepared_config
else:
assert f"proxy_pass https://{sanitised_protocol}" in prepared_config
assert f"proxy_pass http{'s' if tls else ''}://{sanitised_protocol}" in prepared_config


@contextmanager
def mock_resolv_conf(contents: str):
with tempfile.NamedTemporaryFile() as tf:
Path(tf.name).write_text(contents)
with patch("nginx_config.RESOLV_CONF_PATH", tf.name):
yield


@pytest.mark.parametrize(
"mock_contents, expected_dns_ip",
(
(f"foo bar\nnameserver {sample_dns_ip}", sample_dns_ip),
(f"nameserver {sample_dns_ip}\n foo bar baz", sample_dns_ip),
(f"foo bar\nfoo bar\nnameserver {sample_dns_ip}\nnameserver 198.18.0.1", sample_dns_ip),
),
)
def test_dns_ip_addr_getter(mock_contents, expected_dns_ip):
with mock_resolv_conf(mock_contents):
assert _get_dns_ip_address() == expected_dns_ip


def test_dns_ip_addr_fail():
with pytest.raises(RuntimeError):
with mock_resolv_conf("foo bar"):
_get_dns_ip_address()

0 comments on commit 6d15deb

Please sign in to comment.