Skip to content

Commit e4780e1

Browse files
committed
dns shenanigans to get tenant resolution testing working
1 parent ee7c6c9 commit e4780e1

File tree

11 files changed

+201
-12
lines changed

11 files changed

+201
-12
lines changed

misc/python/materialize/mzcompose/composition.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ def __init__(
134134

135135
self.compose: dict[str, Any] = {
136136
"services": {},
137+
"networks": {
138+
"mzcompose": {"ipam": {"config": [{"subnet": "10.10.0.0/24"}]}}
139+
},
137140
}
138141

139142
# Add default volumes

misc/python/materialize/mzcompose/service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class ServiceConfig(TypedDict, total=False):
135135
volumes: list[str]
136136
"""Volumes to attach to the service."""
137137

138-
networks: dict[str, dict[str, list[str]]]
138+
networks: dict[str, dict[str, list[str]]] | dict[str, dict[str, str]] | list[str]
139139
"""Additional networks to join.
140140
141141
TODO(benesch): this should use a nested TypedDict.
@@ -178,6 +178,9 @@ class ServiceConfig(TypedDict, total=False):
178178
user: str | None
179179
"""The user for the container."""
180180

181+
dns: list[str] | None
182+
"""The DNS servers to use for the container."""
183+
181184

182185
class Service:
183186
"""A Docker Compose service in a `Composition`.

misc/python/materialize/mzcompose/services/balancerd.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@
1616

1717

1818
class Balancerd(Service):
19+
1920
def __init__(
2021
self,
2122
name: str = "balancerd",
2223
mzbuild: str = "balancerd",
2324
command: list[str] | None = None,
2425
volumes: list[str] = [],
2526
depends_on: list[str] = [],
27+
networks: (
28+
dict[str, dict[str, list[str]]]
29+
| dict[str, dict[str, str]]
30+
| list[str]
31+
| None
32+
) = None,
33+
dns: list[str] | None = None,
2634
https_resolver_template: str | None = None,
2735
frontegg_resolver_template: str | None = None,
2836
static_resolver_addr: str | None = None,
@@ -56,6 +64,11 @@ def __init__(
5664
"volumes": volumes,
5765
"depends_on": depends_graph,
5866
}
67+
if dns:
68+
config["dns"] = dns
69+
if networks:
70+
config["networks"] = networks
71+
5972
super().__init__(
6073
name=name,
6174
config=config,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright Materialize, Inc. and contributors. All rights reserved.
2+
#
3+
# Use of this software is governed by the Business Source License
4+
# included in the LICENSE file at the root of this repository.
5+
#
6+
# As of the Change Date specified in that file, in accordance with
7+
# the Business Source License, use of this software will be governed
8+
# by the Apache License, Version 2.0.
9+
10+
11+
from typing import Literal, TypedDict
12+
13+
from materialize.mzcompose.service import (
14+
Service,
15+
ServiceConfig,
16+
ServiceDependency,
17+
)
18+
19+
20+
class DnsmasqEntry(TypedDict):
21+
type: Literal["cname", "address"]
22+
key: str
23+
value: str
24+
25+
26+
def dnsmask_entry_to_rec(e: DnsmasqEntry) -> str:
27+
match e["type"]:
28+
case "address":
29+
return f"--entry=address=/{e['key']}/{e['value']}"
30+
case "cname":
31+
return f"--entry=cname={e['key']},{e['value']}"
32+
33+
34+
class Dnsmasq(Service):
35+
36+
def __init__(
37+
self,
38+
name: str = "dnsmasq",
39+
mzbuild: str = "dnsmasq",
40+
command: list[str] | None = None,
41+
depends_on: list[str] = [],
42+
networks: (
43+
dict[str, dict[str, list[str]]]
44+
| dict[str, dict[str, str]]
45+
| list[str]
46+
| None
47+
) = None,
48+
dns_overrides: list[DnsmasqEntry] = [],
49+
) -> None:
50+
51+
command = [dnsmask_entry_to_rec(e) for e in dns_overrides]
52+
53+
depends_graph: dict[str, ServiceDependency] = {
54+
s: {"condition": "service_started"} for s in depends_on
55+
}
56+
config: ServiceConfig = {
57+
"mzbuild": mzbuild,
58+
"command": command,
59+
"ports": [
60+
"53:53/tcp",
61+
"53:53/udp",
62+
],
63+
"depends_on": depends_graph,
64+
"allow_host_ports": True,
65+
}
66+
if networks:
67+
config["networks"] = networks
68+
69+
super().__init__(
70+
name=name,
71+
config=config,
72+
)

misc/python/materialize/mzcompose/services/materialized.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,15 @@ def __init__(
9797
default_replication_factor: int = 1,
9898
listeners_config_path: str = f"{MZ_ROOT}/src/materialized/ci/listener_configs/no_auth.json",
9999
support_external_clusterd: bool = False,
100+
networks: (
101+
dict[str, dict[str, list[str]]] | dict[str, dict[str, str]] | None
102+
) = None,
100103
) -> None:
101104
if name is None:
102105
name = "materialized"
103106

104107
if healthcheck is None:
105108
healthcheck = ["CMD", "curl", "-f", "localhost:6878/api/readyz"]
106-
107109
depends_graph: dict[str, ServiceDependency] = {
108110
s: {"condition": "service_started"} for s in depends_on
109111
}
@@ -338,6 +340,9 @@ def __init__(
338340
volumes += DEFAULT_MZ_VOLUMES
339341
volumes += volumes_extra
340342

343+
if networks:
344+
config["networks"] = networks
345+
341346
config.update(
342347
{
343348
"depends_on": depends_graph,

src/balancerd/src/lib.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,17 +1319,16 @@ impl Resolver {
13191319
}),
13201320
) => {
13211321
let sni_addr = sni_addr_template.replace("{}", servername);
1322-
// let tenant = stub_resolver.tenant(&sni_addr).await;
1322+
let tenant = stub_resolver.tenant(&sni_addr).await;
13231323
let sni_addr = format!("{sni_addr}:{port}");
13241324
let addr = lookup(&sni_addr).await?;
1325-
// if tenant.is_some() {
1326-
// debug!("SNI header found for tenant {:?}", tenant);
1327-
// }
1325+
if tenant.is_some() {
1326+
debug!("SNI header found for tenant {:?}", tenant);
1327+
}
13281328
ResolvedAddr {
13291329
addr,
13301330
password: None,
1331-
tenant: None,
1332-
// tenant,
1331+
tenant,
13331332
}
13341333
}
13351334
_ => {

test/balancerd/mzcompose.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from materialize import MZ_ROOT
3434
from materialize.mzcompose.composition import Composition, Service
3535
from materialize.mzcompose.services.balancerd import Balancerd
36+
from materialize.mzcompose.services.dnsmasq import Dnsmasq, DnsmasqEntry
3637
from materialize.mzcompose.services.frontegg import FronteggMock
3738
from materialize.mzcompose.services.materialized import Materialized
3839
from materialize.mzcompose.services.mz import Mz
@@ -84,10 +85,32 @@ def app_password(email: str) -> str:
8485
SERVICES = [
8586
TestCerts(),
8687
Testdrive(
87-
materialize_url=f"postgres://{quote(ADMIN_USER)}:{app_password(ADMIN_USER)}@balancerd:6875?sslmode=require",
88+
materialize_url=f"postgres://{quote(ADMIN_USER)}:{app_password(ADMIN_USER)}@:6875?sslmode=require",
8889
materialize_use_https=True,
8990
no_reset=True,
9091
),
92+
Dnsmasq(
93+
dns_overrides=[
94+
# Docker compose will insert into all service pods an arec for
95+
# materialized. But what we need is a cname that points to an arec
96+
# with the tenant id in the name and the value should point to the
97+
# ip of materailized. We're going to use a new network for this
98+
# to ensure that we can get a unique ip space and we're going to
99+
# use explicit IPs for dnsmasq to set it it as the dns server for
100+
# balancerd.
101+
DnsmasqEntry(
102+
type="cname",
103+
key="materialized",
104+
value="environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
105+
),
106+
DnsmasqEntry(
107+
type="address",
108+
key="environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
109+
value="10.10.0.4",
110+
),
111+
],
112+
networks={"mzcompose": {"ipv4_address": "10.10.0.2"}},
113+
),
91114
Balancerd(
92115
command=[
93116
"--startup-log-filter=debug",
@@ -115,6 +138,9 @@ def app_password(email: str) -> str:
115138
volumes=[
116139
"secrets:/secrets",
117140
],
141+
# Points to DNSMasq which has an explicit ip
142+
dns=["10.10.0.2"],
143+
networks={"mzcompose": {"ipv4_address": "10.10.0.3"}},
118144
),
119145
FronteggMock(
120146
issuer=FRONTEGG_URL,
@@ -147,6 +173,7 @@ def app_password(email: str) -> str:
147173
"secrets:/secrets",
148174
],
149175
listeners_config_path=f"{MZ_ROOT}/src/materialized/ci/listener_configs/no_auth_https.json",
176+
networks={"mzcompose": {"ipv4_address": "10.10.0.4"}},
150177
),
151178
]
152179

@@ -661,10 +688,9 @@ def workflow_user(c: Composition) -> None:
661688

662689

663690
def workflow_many_connections(c: Composition) -> None:
664-
c.up("balancerd", "frontegg-mock", "materialized")
665-
691+
c.up("balancerd", "dnsmasq", "frontegg-mock", "materialized")
666692
cursors = []
667-
connections = 1000 - 10 # Go almost to the limit, but not above
693+
connections = 100 - 10 # Go almost to the limit, but not above
668694
print(f"Opening {connections} connections.")
669695
start = time.time()
670696
for _ in range(connections):

test/dnsmasq/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM alpine:3.22.1
2+
3+
RUN apk update && apk add dnsmasq
4+
5+
COPY entrypoint.sh /
6+
7+
RUN chmod +x /entrypoint.sh
8+
9+
WORKDIR /
10+
11+
ENTRYPOINT ["/entrypoint.sh"]
12+
13+

test/dnsmasq/dnsmasq.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("dnsmasq")

test/dnsmasq/entrypoint.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
set -euox pipefail
3+
echo "$@"
4+
5+
OVR=/etc/dnsmasq.conf
6+
: > "$OVR"
7+
8+
touch /dns_overrides.conf
9+
chmod 644 /dns_overrides.conf
10+
11+
# Always keep Docker's embedded DNS for container-name resolution.
12+
echo "server=127.0.0.11" >> "$OVR"
13+
14+
# Collect --entry flags: supports --entry=LINE and --entry LINE
15+
while [ $# -gt 0 ]; do
16+
case "$1" in
17+
--entry=*)
18+
printf '%s\n' "${1#--entry=}" >> "$OVR"
19+
;;
20+
--entry)
21+
shift
22+
[ $# -gt 0 ] || { echo "missing value after --entry" >&2; exit 2; }
23+
echo '%s' "$1" >> "$OVR"
24+
;;
25+
--help|-h)
26+
cat <<EOF
27+
Usage: entrypoint.sh [--entry <dnsmasq-directive>]...
28+
Examples:
29+
--entry "address=/target.test.lan/10.0.0.123"
30+
--entry "cname=/alias.test.lan/target.test.lan"
31+
--entry "txt-record=_acme-challenge.app.test.lan,challenge-token"
32+
Notes:
33+
- Lines are written verbatim to $OVR
34+
- Docker DNS passthrough is preserved: server=127.0.0.11
35+
EOF
36+
exit 0
37+
;;
38+
*)
39+
echo "Unknown arg: $1" >&2
40+
exit 2
41+
;;
42+
esac
43+
shift
44+
done
45+
46+
# Run dnsmasq using our generated config
47+
/usr/sbin/dnsmasq \
48+
--no-resolv \
49+
--keep-in-foreground \
50+
--log-queries \
51+
--log-facility=- \
52+
--conf-file="$OVR"

0 commit comments

Comments
 (0)