Skip to content

Commit f721b8a

Browse files
authored
Plumb through option for configuring DNS resolver (#1501)
1 parent 7bf5434 commit f721b8a

5 files changed

Lines changed: 108 additions & 5 deletions

File tree

temporalio/bridge/client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ class ClientHttpConnectProxyConfig:
5757
basic_auth: tuple[str, str] | None
5858

5959

60+
@dataclass
61+
class ClientDnsLoadBalancingConfig:
62+
"""Python representation of the Rust struct for configuring DNS load
63+
balancing.
64+
"""
65+
66+
resolution_interval_millis: int
67+
68+
6069
@dataclass
6170
class ClientConfig:
6271
"""Python representation of the Rust struct for configuring the client."""
@@ -71,6 +80,7 @@ class ClientConfig:
7180
client_name: str
7281
client_version: str
7382
http_connect_proxy_config: ClientHttpConnectProxyConfig | None
83+
dns_load_balancing_config: ClientDnsLoadBalancingConfig | None
7484

7585

7686
@dataclass

temporalio/bridge/src/client.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use temporalio_client::{
1111
ClientKeepAliveOptions as CoreClientKeepAliveConfig, Connection, ConnectionOptions,
1212
DnsLoadBalancingOptions, HttpConnectProxyOptions, RetryOptions,
1313
};
14+
use tracing::warn;
1415
use url::Url;
1516

1617
use crate::runtime;
@@ -35,6 +36,7 @@ pub struct ClientConfig {
3536
retry_config: Option<ClientRetryConfig>,
3637
keep_alive_config: Option<ClientKeepAliveConfig>,
3738
http_connect_proxy_config: Option<ClientHttpConnectProxyConfig>,
39+
dns_load_balancing_config: Option<ClientDnsLoadBalancingConfig>,
3840
}
3941

4042
#[derive(FromPyObject)]
@@ -67,6 +69,11 @@ struct ClientHttpConnectProxyConfig {
6769
pub basic_auth: Option<(String, String)>,
6870
}
6971

72+
#[derive(FromPyObject)]
73+
struct ClientDnsLoadBalancingConfig {
74+
pub resolution_interval_millis: u64,
75+
}
76+
7077
#[derive(FromPyObject)]
7178
pub(crate) struct RpcCall {
7279
pub(crate) rpc: String,
@@ -236,6 +243,15 @@ impl ClientConfig {
236243
) -> PyResult<ConnectionOptions> {
237244
let (ascii_headers, binary_headers) = partition_headers(self.metadata);
238245
let has_proxy = self.http_connect_proxy_config.is_some();
246+
// Core rejects DNS load balancing alongside an HTTP CONNECT proxy, so
247+
// suppress DNS LB whenever a proxy is configured to keep the
248+
// pre-existing behavior even if a caller leaves the default.
249+
let dns_load_balancing = if has_proxy {
250+
warn!("Disabling DNS load balancing because http_connect_proxy_config is set");
251+
None
252+
} else {
253+
self.dns_load_balancing_config.map(Into::into)
254+
};
239255
let conn_opts = ConnectionOptions::new(
240256
Url::parse(&self.target_url)
241257
.map_err(|err| PyValueError::new_err(format!("invalid target URL: {err}")))?,
@@ -249,11 +265,7 @@ impl ClientConfig {
249265
)
250266
.keep_alive(self.keep_alive_config.map(Into::into))
251267
.maybe_http_connect_proxy(self.http_connect_proxy_config.map(Into::into))
252-
.dns_load_balancing(if has_proxy {
253-
None
254-
} else {
255-
Some(DnsLoadBalancingOptions::default())
256-
})
268+
.dns_load_balancing(dns_load_balancing)
257269
.headers(ascii_headers)
258270
.binary_headers(binary_headers)
259271
.maybe_api_key(self.api_key)
@@ -322,3 +334,11 @@ impl From<ClientHttpConnectProxyConfig> for HttpConnectProxyOptions {
322334
}
323335
}
324336
}
337+
338+
impl From<ClientDnsLoadBalancingConfig> for DnsLoadBalancingOptions {
339+
fn from(conf: ClientDnsLoadBalancingConfig) -> Self {
340+
let mut opts = DnsLoadBalancingOptions::default();
341+
opts.resolution_interval = Duration::from_millis(conf.resolution_interval_millis);
342+
opts
343+
}
344+
}

temporalio/client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
)
7575
from temporalio.service import (
7676
ConnectConfig,
77+
DnsLoadBalancingConfig,
7778
HttpConnectProxyConfig,
7879
KeepAliveConfig,
7980
RetryConfig,
@@ -139,6 +140,8 @@ async def connect(
139140
lazy: bool = False,
140141
runtime: temporalio.runtime.Runtime | None = None,
141142
http_connect_proxy_config: HttpConnectProxyConfig | None = None,
143+
dns_load_balancing_config: DnsLoadBalancingConfig
144+
| None = DnsLoadBalancingConfig.default,
142145
header_codec_behavior: HeaderCodecBehavior = HeaderCodecBehavior.NO_CODEC,
143146
) -> Self:
144147
"""Connect to a Temporal server.
@@ -194,6 +197,11 @@ async def connect(
194197
used for workers.
195198
runtime: The runtime for this client, or the default if unset.
196199
http_connect_proxy_config: Configuration for HTTP CONNECT proxy.
200+
dns_load_balancing_config: DNS load balancing configuration for the
201+
client connection. Default is to re-resolve DNS every 30s. Can
202+
be set to ``None`` to disable. Silently disabled when
203+
``http_connect_proxy_config`` is set, since the two are mutually
204+
exclusive.
197205
header_codec_behavior: Encoding behavior for headers sent by the client.
198206
"""
199207
connect_config = temporalio.service.ConnectConfig(
@@ -207,6 +215,7 @@ async def connect(
207215
lazy=lazy,
208216
runtime=runtime,
209217
http_connect_proxy_config=http_connect_proxy_config,
218+
dns_load_balancing_config=dns_load_balancing_config,
210219
)
211220

212221
def make_lambda(
@@ -2873,6 +2882,7 @@ class ClientConnectConfig(TypedDict, total=False):
28732882
lazy: bool
28742883
runtime: temporalio.runtime.Runtime | None
28752884
http_connect_proxy_config: HttpConnectProxyConfig | None
2885+
dns_load_balancing_config: DnsLoadBalancingConfig | None
28762886
header_codec_behavior: HeaderCodecBehavior
28772887

28782888

@@ -9774,6 +9784,7 @@ async def connect(
97749784
lazy: bool = False,
97759785
runtime: temporalio.runtime.Runtime | None = None,
97769786
http_connect_proxy_config: HttpConnectProxyConfig | None = None,
9787+
dns_load_balancing_config: DnsLoadBalancingConfig | None = None,
97779788
) -> CloudOperationsClient:
97789789
"""Connect to a Temporal Cloud Operations API.
97799790
@@ -9810,6 +9821,10 @@ async def connect(
98109821
used for workers.
98119822
runtime: The runtime for this client, or the default if unset.
98129823
http_connect_proxy_config: Configuration for HTTP CONNECT proxy.
9824+
dns_load_balancing_config: DNS load balancing configuration for the
9825+
client connection. Default is disabled. Silently disabled when
9826+
``http_connect_proxy_config`` is set, since the two are mutually
9827+
exclusive.
98139828
"""
98149829
# Add version if given
98159830
if version:
@@ -9826,6 +9841,7 @@ async def connect(
98269841
lazy=lazy,
98279842
runtime=runtime,
98289843
http_connect_proxy_config=http_connect_proxy_config,
9844+
dns_load_balancing_config=dns_load_balancing_config,
98299845
)
98309846
return CloudOperationsClient(
98319847
await temporalio.service.ServiceClient.connect(connect_config)

temporalio/service.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ def _to_bridge_config(
132132
)
133133

134134

135+
@dataclass(frozen=True)
136+
class DnsLoadBalancingConfig:
137+
"""DNS load balancing configuration for client connections.
138+
139+
When enabled, Core periodically re-resolves the target host's DNS records
140+
and round-robins requests across the resolved addresses. Cannot be used
141+
together with :py:class:`HttpConnectProxyConfig` -- DNS load balancing is
142+
silently disabled when an HTTP CONNECT proxy is configured.
143+
"""
144+
145+
resolution_interval_millis: int = 30000
146+
"""How often to re-resolve DNS, in milliseconds."""
147+
default: ClassVar[DnsLoadBalancingConfig]
148+
"""Default DNS load balancing config."""
149+
150+
def _to_bridge_config(
151+
self,
152+
) -> temporalio.bridge.client.ClientDnsLoadBalancingConfig:
153+
return temporalio.bridge.client.ClientDnsLoadBalancingConfig(
154+
resolution_interval_millis=self.resolution_interval_millis,
155+
)
156+
157+
158+
DnsLoadBalancingConfig.default = DnsLoadBalancingConfig()
159+
160+
135161
@dataclass
136162
class ConnectConfig:
137163
"""Config for connecting to the server."""
@@ -146,6 +172,9 @@ class ConnectConfig:
146172
lazy: bool = False
147173
runtime: temporalio.runtime.Runtime | None = None
148174
http_connect_proxy_config: HttpConnectProxyConfig | None = None
175+
dns_load_balancing_config: DnsLoadBalancingConfig | None = (
176+
DnsLoadBalancingConfig.default
177+
)
149178

150179
def __post_init__(self) -> None:
151180
"""Set extra defaults on unset properties."""
@@ -203,6 +232,11 @@ def _to_bridge_config(self) -> temporalio.bridge.client.ClientConfig:
203232
if self.http_connect_proxy_config
204233
else None
205234
),
235+
dns_load_balancing_config=(
236+
self.dns_load_balancing_config._to_bridge_config()
237+
if self.dns_load_balancing_config
238+
else None
239+
),
206240
)
207241

208242

tests/test_service.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,29 @@ def test_connect_config_tls_explicit_config_preserved():
219219
assert config.tls == tls_config
220220

221221

222+
def test_connect_config_dns_load_balancing_custom():
223+
"""Custom DnsLoadBalancingConfig is forwarded to the bridge unchanged."""
224+
config = temporalio.service.ConnectConfig(
225+
target_host="localhost:7233",
226+
dns_load_balancing_config=temporalio.service.DnsLoadBalancingConfig(
227+
resolution_interval_millis=5000,
228+
),
229+
)
230+
bridge_config = config._to_bridge_config()
231+
assert bridge_config.dns_load_balancing_config is not None
232+
assert bridge_config.dns_load_balancing_config.resolution_interval_millis == 5000
233+
234+
235+
def test_connect_config_dns_load_balancing_disabled():
236+
"""Setting dns_load_balancing_config=None forwards None to the bridge."""
237+
config = temporalio.service.ConnectConfig(
238+
target_host="localhost:7233",
239+
dns_load_balancing_config=None,
240+
)
241+
bridge_config = config._to_bridge_config()
242+
assert bridge_config.dns_load_balancing_config is None
243+
244+
222245
async def test_rpc_execution_not_unknown(client: Client):
223246
"""
224247
Execute each rpc method and expect a failure, but ensure the failure is not that the rpc method is unknown

0 commit comments

Comments
 (0)