1mod codec;
20mod dyncfgs;
21
22use std::collections::BTreeMap;
23use std::net::{IpAddr, SocketAddr};
24use std::path::PathBuf;
25use std::pin::Pin;
26use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
27use std::sync::{Arc, Mutex};
28use std::time::{Duration, Instant};
29
30use anyhow::Context;
31use axum::{Router, routing};
32use bytes::BytesMut;
33use futures::TryFutureExt;
34use futures::stream::BoxStream;
35use hickory_resolver::config::LookupIpStrategy;
36use hickory_resolver::lookup_ip::LookupIp;
37use hickory_resolver::net::runtime::TokioRuntimeProvider;
38use hickory_resolver::proto::rr::{RData, RecordType};
39use hickory_resolver::system_conf::read_system_conf;
40use hickory_resolver::{Resolver, TokioResolver};
41use hyper::StatusCode;
42use hyper_util::rt::TokioIo;
43use launchdarkly_server_sdk as ld;
44use mz_build_info::{BuildInfo, build_info};
45use mz_dyncfg::ConfigSet;
46use mz_frontegg_auth::Authenticator as FronteggAuthentication;
47use mz_orchestrator_tracing::NO_SENTRY_TARGET;
48use mz_ore::cast::CastFrom;
49use mz_ore::id_gen::conn_id_org_uuid;
50use mz_ore::metrics::{
51 ComputedGauge, ComputedIntGauge, ComputedUIntGauge, IntCounter, IntGauge, MetricsRegistry,
52};
53use mz_ore::netio::AsyncReady;
54use mz_ore::now::{NowFn, SYSTEM_TIME, epoch_to_uuid_v7};
55use mz_ore::task::{JoinSetExt, spawn};
56use mz_ore::tracing::TracingHandle;
57use mz_ore::{metric, netio};
58use mz_pgwire_common::{
59 ACCEPT_SSL_ENCRYPTION, CONN_UUID_KEY, Conn, ErrorResponse, FrontendMessage,
60 FrontendStartupMessage, MAX_STARTUP_FRAME_SIZE, MZ_FORWARDED_FOR_KEY, REJECT_ENCRYPTION,
61 VERSION_3, decode_startup,
62};
63use mz_server_core::{
64 Connection, ConnectionStream, ListenerHandle, ReloadTrigger, ReloadingSslContext,
65 ReloadingTlsConfig, ServeConfig, ServeDyncfg, TlsCertConfig, TlsMode, listen,
66};
67use openssl::error::ErrorStack;
68use openssl::ssl::{NameType, Ssl, SslConnector, SslMethod, SslVerifyMode};
69use prometheus::{IntCounterVec, IntGaugeVec};
70use proxy_header::{ProxiedAddress, ProxyHeader};
71use semver::Version;
72use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt};
73use tokio::net::TcpStream;
74use tokio::sync::oneshot;
75use tokio::task::JoinSet;
76use tokio_metrics::TaskMetrics;
77use tokio_openssl::SslStream;
78use tokio_postgres::error::SqlState;
79use tower::Service;
80use tracing::{debug, error, info, warn};
81use uuid::Uuid;
82
83use crate::codec::{BackendMessage, FramedConn};
84use crate::dyncfgs::{
85 CONNECTION_HIGH_WATERMARK, CONNECTION_LOW_WATERMARK, INJECT_PROXY_PROTOCOL_HEADER_HTTP,
86 MAX_CONNECTIONS, PRE_RESOLVED_TIMEOUT, SIGTERM_CONNECTION_WAIT, SIGTERM_LISTEN_WAIT,
87 has_tracing_config_update, tracing_config,
88};
89
90pub const BUILD_INFO: BuildInfo = build_info!();
92
93pub struct BalancerConfig {
94 build_version: Version,
96 internal_http_listen_addr: SocketAddr,
98 pgwire_listen_addr: SocketAddr,
100 https_listen_addr: SocketAddr,
102 cancellation_resolver: CancellationResolver,
104 resolver: BalancerResolver,
106 https_sni_addr_template: String,
107 tls: Option<TlsCertConfig>,
108 internal_tls: bool,
109 metrics_registry: MetricsRegistry,
110 reload_certs: BoxStream<'static, Option<oneshot::Sender<Result<(), anyhow::Error>>>>,
111 launchdarkly_sdk_key: Option<String>,
112 config_sync_file_path: Option<PathBuf>,
113 config_sync_timeout: Duration,
114 config_sync_loop_interval: Option<Duration>,
115 cloud_provider: Option<String>,
116 cloud_provider_region: Option<String>,
117 tracing_handle: TracingHandle,
118 default_configs: Vec<(String, String)>,
119}
120
121impl BalancerConfig {
122 pub fn new(
123 build_info: &BuildInfo,
124 internal_http_listen_addr: SocketAddr,
125 pgwire_listen_addr: SocketAddr,
126 https_listen_addr: SocketAddr,
127 cancellation_resolver: CancellationResolver,
128 resolver: BalancerResolver,
129 https_sni_addr_template: String,
130 tls: Option<TlsCertConfig>,
131 internal_tls: bool,
132 metrics_registry: MetricsRegistry,
133 reload_certs: ReloadTrigger,
134 launchdarkly_sdk_key: Option<String>,
135 config_sync_file: Option<PathBuf>,
136 config_sync_timeout: Duration,
137 config_sync_loop_interval: Option<Duration>,
138 cloud_provider: Option<String>,
139 cloud_provider_region: Option<String>,
140 tracing_handle: TracingHandle,
141 default_configs: Vec<(String, String)>,
142 ) -> Self {
143 Self {
144 build_version: build_info.semver_version(),
145 internal_http_listen_addr,
146 pgwire_listen_addr,
147 https_listen_addr,
148 cancellation_resolver,
149 resolver,
150 https_sni_addr_template,
151 tls,
152 internal_tls,
153 metrics_registry,
154 reload_certs,
155 launchdarkly_sdk_key,
156 config_sync_file_path: config_sync_file,
157 config_sync_timeout,
158 config_sync_loop_interval,
159 cloud_provider,
160 cloud_provider_region,
161 tracing_handle,
162 default_configs,
163 }
164 }
165}
166
167#[derive(Debug)]
169pub struct BalancerMetrics {
170 _uptime: ComputedGauge,
171}
172
173impl BalancerMetrics {
174 pub fn new(cfg: &BalancerConfig) -> Self {
176 let start = Instant::now();
177 let uptime = cfg.metrics_registry.register_computed_gauge(
178 metric!(
179 name: "mz_balancer_metadata_seconds",
180 help: "server uptime, labels are build metadata",
181 const_labels: {
182 "version" => cfg.build_version,
183 "build_type" => if cfg!(release) { "release" } else { "debug" }
184 },
185 ),
186 move || start.elapsed().as_secs_f64(),
187 );
188 BalancerMetrics { _uptime: uptime }
189 }
190}
191
192pub struct BalancerService {
193 cfg: BalancerConfig,
194 pub pgwire: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
195 pub https: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
196 pub internal_http: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
197 _metrics: BalancerMetrics,
198 configs: ConfigSet,
199}
200
201impl BalancerService {
202 pub async fn new(cfg: BalancerConfig) -> Result<Self, anyhow::Error> {
203 let pgwire = listen(&cfg.pgwire_listen_addr)?;
204 let https = listen(&cfg.https_listen_addr)?;
205 let internal_http = listen(&cfg.internal_http_listen_addr)?;
206 let metrics = BalancerMetrics::new(&cfg);
207 let mut configs = ConfigSet::default();
208 configs = dyncfgs::all_dyncfgs(configs);
209 dyncfgs::set_defaults(&configs, cfg.default_configs.clone())?;
210 let tracing_handle = cfg.tracing_handle.clone();
211 match (
213 cfg.launchdarkly_sdk_key.as_deref(),
214 cfg.config_sync_file_path.as_deref(),
215 ) {
216 (Some(key), None) => {
217 let _ = mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
218 configs.clone(),
219 &BUILD_INFO,
220 |builder| {
221 let region = cfg
222 .cloud_provider_region
223 .clone()
224 .unwrap_or_else(|| String::from("unknown"));
225 if let Some(provider) = cfg.cloud_provider.clone() {
226 builder.add_context(
227 ld::ContextBuilder::new(format!(
228 "{}/{}/{}",
229 provider, region, cfg.build_version
230 ))
231 .kind("balancer")
232 .set_string("provider", provider)
233 .set_string("region", region)
234 .set_string("version", cfg.build_version.to_string())
235 .build()
236 .map_err(|e| anyhow::anyhow!(e))?,
237 );
238 } else {
239 builder.add_context(
240 ld::ContextBuilder::new(format!(
241 "{}/{}/{}",
242 "unknown", region, cfg.build_version
243 ))
244 .anonymous(true) .kind("balancer")
246 .set_string("provider", "unknown")
247 .set_string("region", region)
248 .set_string("version", cfg.build_version.to_string())
249 .build()
250 .map_err(|e| anyhow::anyhow!(e))?,
251 );
252 }
253 Ok(())
254 },
255 Some(key),
256 cfg.config_sync_timeout,
257 cfg.config_sync_loop_interval,
258 move |updates, configs| {
259 if has_tracing_config_update(updates) {
260 match tracing_config(configs) {
261 Ok(parameters) => parameters.apply(&tracing_handle),
262 Err(err) => warn!("unable to update tracing: {err}"),
263 }
264 }
265 },
266 )
267 .await
268 .inspect_err(|e| warn!("LaunchDarkly sync error: {e}"));
269 }
270 (None, Some(path)) => {
271 let _ = mz_dyncfg_file::sync_file_to_configset(
272 configs.clone(),
273 path,
274 cfg.config_sync_timeout,
275 cfg.config_sync_loop_interval,
276 move |updates, configs| {
277 if has_tracing_config_update(updates) {
278 match tracing_config(configs) {
279 Ok(parameters) => parameters.apply(&tracing_handle),
280 Err(err) => warn!("unable to update tracing: {err}"),
281 }
282 }
283 },
284 )
285 .await
286 .inspect_err(|e| warn!("File config sync error: {e}"));
292 }
293 (Some(_), Some(_)) => panic!(
294 "must provide either config_sync_file_path or launchdarkly_sdk_key for config syncing",
295 ),
296 (None, None) => {}
297 };
298 Ok(Self {
299 cfg,
300 pgwire,
301 https,
302 internal_http,
303 _metrics: metrics,
304 configs,
305 })
306 }
307
308 pub async fn serve(self) -> Result<(), anyhow::Error> {
309 let (pgwire_tls, https_tls) = match &self.cfg.tls {
310 Some(tls) => {
311 let context = tls.reloading_context(self.cfg.reload_certs)?;
312 (
313 Some(ReloadingTlsConfig {
314 context: context.clone(),
315 mode: TlsMode::Require,
316 }),
317 Some(context),
318 )
319 }
320 None => (None, None),
321 };
322
323 let internal_tls = self
326 .cfg
327 .internal_tls
328 .then(internal_tls_connector)
329 .transpose()
330 .context("building internal TLS connector")?;
331
332 let metrics = ServerMetricsConfig::register_into(&self.cfg.metrics_registry);
333 let limiter = ConnectionLimiter::new(&self.cfg.metrics_registry, self.configs.clone());
334
335 let mut set = JoinSet::new();
336 let mut server_handles = Vec::new();
337 let pgwire_addr = self.pgwire.0.local_addr();
338 let https_addr = self.https.0.local_addr();
339 let internal_http_addr = self.internal_http.0.local_addr();
340
341 let shared_dns = match self.cfg.resolver.shared_dns() {
346 Some(dns) => dns,
347 None => Arc::new(TenantDnsResolver::new()?),
348 };
349
350 {
351 let pgwire = PgwireBalancer {
352 resolver: Arc::new(self.cfg.resolver),
353 cancellation_resolver: Arc::new(self.cfg.cancellation_resolver),
354 tls: pgwire_tls,
355 internal_tls: internal_tls.clone(),
356 metrics: ServerMetrics::new(metrics.clone(), "pgwire"),
357 limiter: Arc::clone(&limiter),
358 configs: self.configs.clone(),
359 now: SYSTEM_TIME.clone(),
360 };
361 let (handle, stream) = self.pgwire;
362 server_handles.push(handle);
363 set.spawn_named(|| "pgwire_stream", {
364 let config_set = self.configs.clone();
365 async move {
366 mz_server_core::serve(ServeConfig {
367 server: pgwire,
368 conns: stream,
369 dyncfg: Some(ServeDyncfg {
370 config_set,
371 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
372 }),
373 })
374 .await;
375 warn!("pgwire server exited");
376 }
377 });
378 }
379 {
380 let Some((addr, port)) = self.cfg.https_sni_addr_template.split_once(':') else {
381 panic!("expected port in https_addr_template");
382 };
383 let port: u16 = port.parse().expect("unexpected port");
384
385 let https = HttpsBalancer {
386 resolver: shared_dns,
387 tls: https_tls,
388 resolve_template: Arc::from(addr),
389 port,
390 metrics: Arc::from(ServerMetrics::new(metrics, "https")),
391 limiter: Arc::clone(&limiter),
392 configs: self.configs.clone(),
393 internal_tls,
394 };
395 let (handle, stream) = self.https;
396 server_handles.push(handle);
397 set.spawn_named(|| "https_stream", {
398 let config_set = self.configs.clone();
399 async move {
400 mz_server_core::serve(ServeConfig {
401 server: https,
402 conns: stream,
403 dyncfg: Some(ServeDyncfg {
404 config_set,
405 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
406 }),
407 })
408 .await;
409 warn!("https server exited");
410 }
411 });
412 }
413 {
414 let router = Router::new()
415 .route(
416 "/metrics",
417 routing::get(move |headers: axum::http::HeaderMap| async move {
418 mz_http_util::handle_prometheus(&self.cfg.metrics_registry, headers).await
419 }),
420 )
421 .route(
422 "/api/livez",
423 routing::get(mz_http_util::handle_liveness_check),
424 )
425 .route("/api/readyz", {
426 let limiter = Arc::clone(&limiter);
427 routing::get(move || async move {
428 if limiter.evaluate_watermarks() {
429 (StatusCode::OK, "ready")
430 } else {
431 (
432 StatusCode::SERVICE_UNAVAILABLE,
433 "at the connection high watermark",
434 )
435 }
436 })
437 });
438 let internal_http = InternalHttpServer { router };
439 let (handle, stream) = self.internal_http;
440 server_handles.push(handle);
441 set.spawn_named(|| "internal_http_stream", async move {
442 mz_server_core::serve(ServeConfig {
443 server: internal_http,
444 conns: stream,
445 dyncfg: None,
448 })
449 .await;
450 warn!("internal_http server exited");
451 });
452 }
453 spawn(|| "connection_watermark_tick", async move {
456 let mut tick = tokio::time::interval(Duration::from_secs(1));
457 loop {
458 tick.tick().await;
459 limiter.evaluate_watermarks();
460 }
461 });
462 #[cfg(unix)]
463 {
464 let mut sigterm =
465 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
466 set.spawn_named(|| "sigterm_handler", async move {
467 sigterm.recv().await;
468 let wait = SIGTERM_LISTEN_WAIT.get(&self.configs);
469 warn!("received signal TERM - delaying for {:?}!", wait);
470 tokio::time::sleep(wait).await;
471 warn!("sigterm delay complete, dropping server handles");
472 drop(server_handles);
473 });
474 }
475
476 println!("balancerd {} listening...", BUILD_INFO.human_version(None));
477 println!(" TLS enabled: {}", self.cfg.tls.is_some());
478 println!(" pgwire address: {}", pgwire_addr);
479 println!(" HTTPS address: {}", https_addr);
480 println!(" internal HTTP address: {}", internal_http_addr);
481
482 while let Some(res) = set.join_next().await {
484 if let Err(err) = res {
485 error!("serving task failed: {err}")
486 }
487 }
488 Ok(())
489 }
490}
491
492struct InternalHttpServer {
493 router: Router,
494}
495
496impl mz_server_core::Server for InternalHttpServer {
497 const NAME: &'static str = "internal_http";
498
499 fn handle_connection(
501 &self,
502 conn: Connection,
503 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
504 ) -> mz_server_core::ConnectionHandler {
505 let router = self.router.clone();
506 let service = hyper::service::service_fn(move |req| router.clone().call(req));
507 let conn = TokioIo::new(conn);
508
509 Box::pin(async {
510 let http = hyper::server::conn::http1::Builder::new();
511 http.serve_connection(conn, service).err_into().await
512 })
513 }
514}
515
516fn internal_tls_connector() -> Result<SslConnector, ErrorStack> {
518 let mut builder = SslConnector::builder(SslMethod::tls())?;
519 builder.set_verify(SslVerifyMode::NONE);
521 Ok(builder.build())
522}
523
524struct GaugeGuard {
527 gauge: IntGauge,
528}
529
530impl From<IntGauge> for GaugeGuard {
531 fn from(gauge: IntGauge) -> Self {
532 let _self = Self { gauge };
533 _self.gauge.inc();
534 _self
535 }
536}
537
538impl Drop for GaugeGuard {
539 fn drop(&mut self) {
540 self.gauge.dec();
541 }
542}
543
544#[derive(Clone, Debug)]
545struct ServerMetricsConfig {
546 connection_status: IntCounterVec,
547 active_connections: IntGaugeVec,
548 pre_resolved_connections: IntGaugeVec,
549 pre_resolved_timeouts: IntCounterVec,
550 tenant_connections: IntGaugeVec,
551 tenant_connection_rx: IntCounterVec,
552 tenant_connection_tx: IntCounterVec,
553 tenant_pgwire_sni_count: IntCounterVec,
554}
555
556impl ServerMetricsConfig {
557 fn register_into(registry: &MetricsRegistry) -> Self {
558 let connection_status = registry.register(metric!(
559 name: "mz_balancer_connection_status",
560 help: "Count of completed network connections, by status",
561 var_labels: ["source", "status"],
562 ));
563 let active_connections = registry.register(metric!(
564 name: "mz_balancer_connection_active",
565 help: "Count of currently open network connections.",
566 var_labels: ["source"],
567 ));
568 let pre_resolved_connections = registry.register(metric!(
569 name: "mz_balancer_pre_resolved_connection_active",
570 help: "Count of open network connections that have not yet resolved a backend.",
571 var_labels: ["source"],
572 ));
573 let pre_resolved_timeouts = registry.register(metric!(
574 name: "mz_balancer_pre_resolved_timeout_total",
575 help: "Count of connections closed for not resolving a backend within the pre-resolved timeout.",
576 var_labels: ["source"],
577 ));
578 let tenant_connections = registry.register(metric!(
579 name: "mz_balancer_tenant_connection_active",
580 help: "Count of opened network connections by tenant.",
581 var_labels: ["source", "tenant"]
582 ));
583 let tenant_connection_rx = registry.register(metric!(
584 name: "mz_balancer_tenant_connection_rx",
585 help: "Number of bytes received from a client for a tenant.",
586 var_labels: ["source", "tenant"],
587 ));
588 let tenant_connection_tx = registry.register(metric!(
589 name: "mz_balancer_tenant_connection_tx",
590 help: "Number of bytes sent to a client for a tenant.",
591 var_labels: ["source", "tenant"],
592 ));
593 let tenant_pgwire_sni_count = registry.register(metric!(
594 name: "mz_balancer_tenant_pgwire_sni_count",
595 help: "Count of pgwire connections that have and do not have SNI available per tenant.",
596 var_labels: ["tenant", "has_sni"],
597 ));
598 Self {
599 connection_status,
600 active_connections,
601 pre_resolved_connections,
602 pre_resolved_timeouts,
603 tenant_connections,
604 tenant_connection_rx,
605 tenant_connection_tx,
606 tenant_pgwire_sni_count,
607 }
608 }
609}
610
611#[derive(Clone, Debug)]
612struct ServerMetrics {
613 inner: ServerMetricsConfig,
614 source: &'static str,
615}
616
617impl ServerMetrics {
618 fn new(inner: ServerMetricsConfig, source: &'static str) -> Self {
619 let self_ = Self { inner, source };
620
621 self_.connection_status(false);
624 self_.connection_status(true);
625 drop(self_.active_connections());
626
627 self_
628 }
629
630 fn connection_status(&self, is_ok: bool) -> IntCounter {
631 self.inner
632 .connection_status
633 .with_label_values(&[self.source, Self::status_label(is_ok)])
634 }
635
636 fn active_connections(&self) -> GaugeGuard {
637 self.inner
638 .active_connections
639 .with_label_values(&[self.source])
640 .into()
641 }
642
643 fn pre_resolved_connections(&self) -> GaugeGuard {
644 self.inner
645 .pre_resolved_connections
646 .with_label_values(&[self.source])
647 .into()
648 }
649
650 fn pre_resolved_timeout(&self) -> IntCounter {
651 self.inner
652 .pre_resolved_timeouts
653 .with_label_values(&[self.source])
654 }
655
656 fn tenant_connections(&self, tenant: &str) -> GaugeGuard {
657 self.inner
658 .tenant_connections
659 .with_label_values(&[self.source, tenant])
660 .into()
661 }
662
663 fn tenant_connections_rx(&self, tenant: &str) -> IntCounter {
664 self.inner
665 .tenant_connection_rx
666 .with_label_values(&[self.source, tenant])
667 }
668
669 fn tenant_connections_tx(&self, tenant: &str) -> IntCounter {
670 self.inner
671 .tenant_connection_tx
672 .with_label_values(&[self.source, tenant])
673 }
674
675 fn tenant_pgwire_sni_count(&self, tenant: &str, has_sni: bool) -> IntCounter {
676 self.inner
677 .tenant_pgwire_sni_count
678 .with_label_values(&[tenant, &has_sni.to_string()])
679 }
680
681 fn status_label(is_ok: bool) -> &'static str {
682 if is_ok { "success" } else { "error" }
683 }
684}
685
686#[derive(Debug)]
693struct ConnectionLimiter {
694 configs: ConfigSet,
695 active: AtomicU32,
696 limited: AtomicBool,
699 accepting: Arc<AtomicBool>,
701 watermark_state: Mutex<WatermarkState>,
704 rejected: IntCounter,
705 watermark_transitions: IntCounter,
706 _limit: ComputedUIntGauge,
707 _accepting: ComputedIntGauge,
708 _high_watermark: ComputedUIntGauge,
709 _low_watermark: ComputedUIntGauge,
710}
711
712#[derive(Debug, Default)]
713struct WatermarkState {
714 out_of_rotation: bool,
715 last_warning: Option<String>,
717}
718
719#[derive(Debug, Clone, Copy)]
721struct Watermarks {
722 low: usize,
723 high: usize,
724}
725
726fn configured_watermarks(configs: &ConfigSet) -> Result<Option<Watermarks>, String> {
733 let low = CONNECTION_LOW_WATERMARK.get(configs);
734 let high = CONNECTION_HIGH_WATERMARK.get(configs);
735 let max = MAX_CONNECTIONS.get(configs);
736 let reason = match (low, high) {
737 (0, 0) => return Ok(None),
738 (0, _) | (_, 0) => "both watermarks must be set",
739 (low, high) if low >= high => "the low watermark must be below the high watermark",
740 _ if max == 0 => "balancerd_max_connections must be enabled",
741 (_, high) if high >= usize::cast_from(max) => {
742 "the high watermark must be below balancerd_max_connections"
743 }
744 (low, high) => return Ok(Some(Watermarks { low, high })),
745 };
746 Err(format!(
747 "{reason} (low={low}, high={high}, max_connections={max})"
748 ))
749}
750
751fn watermark_metric(configs: &ConfigSet, pick: fn(Watermarks) -> usize) -> u64 {
753 configured_watermarks(configs)
754 .ok()
755 .flatten()
756 .map_or(0, |w| u64::cast_from(pick(w)))
757}
758
759impl ConnectionLimiter {
760 fn new(registry: &MetricsRegistry, configs: ConfigSet) -> Arc<Self> {
761 let rejected = registry.register(metric!(
762 name: "mz_balancer_connection_rejected_total",
763 help: "Count of connections refused because the connection limit was reached.",
764 ));
765 let limit = registry.register_computed_gauge(
766 metric!(
767 name: "mz_balancer_connection_limit",
768 help: "Maximum number of connections proxied at once, 0 if unlimited.",
769 ),
770 {
771 let configs = configs.clone();
772 move || u64::from(MAX_CONNECTIONS.get(&configs))
773 },
774 );
775 let watermark_transitions = registry.register(metric!(
776 name: "mz_balancer_connection_watermark_transitions_total",
777 help: "Count of times balancerd left or returned to load balancer rotation because \
778 of the connection watermarks.",
779 ));
780 let accepting = Arc::new(AtomicBool::new(true));
781 let accepting_gauge: ComputedIntGauge = registry.register_computed_gauge(
782 metric!(
783 name: "mz_balancer_connection_accepting",
784 help: "Whether /api/readyz reports ready: 1 in load balancer rotation, 0 out.",
785 ),
786 {
787 let accepting = Arc::clone(&accepting);
788 move || i64::from(accepting.load(Ordering::Relaxed))
789 },
790 );
791 let high_watermark = registry.register_computed_gauge(
793 metric!(
794 name: "mz_balancer_connection_high_watermark",
795 help: "Connections at which balancerd leaves load balancer rotation, 0 if disabled.",
796 ),
797 {
798 let configs = configs.clone();
799 move || watermark_metric(&configs, |w| w.high)
800 },
801 );
802 let low_watermark = registry.register_computed_gauge(
803 metric!(
804 name: "mz_balancer_connection_low_watermark",
805 help: "Connections below which balancerd returns to load balancer rotation, 0 if \
806 disabled.",
807 ),
808 {
809 let configs = configs.clone();
810 move || watermark_metric(&configs, |w| w.low)
811 },
812 );
813 Arc::new(ConnectionLimiter {
814 configs,
815 active: AtomicU32::new(0),
816 limited: AtomicBool::new(false),
817 accepting,
818 watermark_state: Mutex::new(WatermarkState::default()),
819 rejected,
820 watermark_transitions,
821 _limit: limit,
822 _accepting: accepting_gauge,
823 _high_watermark: high_watermark,
824 _low_watermark: low_watermark,
825 })
826 }
827
828 #[cfg(test)]
829 fn accepting(&self) -> bool {
830 self.accepting.load(Ordering::Relaxed)
831 }
832
833 fn evaluate_watermarks(&self) -> bool {
845 let mut state = self.watermark_state.lock().expect("lock poisoned");
846 let watermarks = self.watermarks(&mut state.last_warning);
847 let active = usize::cast_from(self.active.load(Ordering::SeqCst));
848 let accepting = match (watermarks, state.out_of_rotation) {
849 (Some(w), false) if active >= w.high => {
850 warn!(
851 "leaving load balancer rotation: {active} active connections reached the \
852 high watermark of {}",
853 w.high
854 );
855 false
856 }
857 (Some(w), true) if active < w.low => {
858 info!(
859 "returning to load balancer rotation: {active} active connections, below \
860 the low watermark of {}",
861 w.low
862 );
863 true
864 }
865 (None, true) => {
868 info!(
869 "returning to load balancer rotation: connection watermarks no longer in effect"
870 );
871 true
872 }
873 _ => return !state.out_of_rotation,
874 };
875 state.out_of_rotation = !accepting;
876 self.accepting.store(accepting, Ordering::Relaxed);
877 self.watermark_transitions.inc();
878 accepting
879 }
880
881 fn watermarks(&self, last: &mut Option<String>) -> Option<Watermarks> {
882 match configured_watermarks(&self.configs) {
883 Ok(watermarks) => {
884 *last = None;
885 watermarks
886 }
887 Err(reason) => {
888 if last.as_deref() != Some(&reason) {
889 warn!("ignoring connection watermarks: {reason}");
890 *last = Some(reason);
891 }
892 None
893 }
894 }
895 }
896
897 fn acquire(self: &Arc<Self>) -> Option<ConnectionGuard> {
900 let limit = MAX_CONNECTIONS.get(&self.configs);
901 let unlimited = limit == 0;
902 match self
903 .active
904 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
905 (unlimited || active < limit).then(|| active + 1)
906 }) {
907 Ok(prev) => {
908 if unlimited || prev + 1 <= limit - limit / 10 {
911 if self.limited.swap(false, Ordering::Relaxed) {
912 info!("accepting new connections again, limit is {limit}");
913 }
914 }
915 Some(ConnectionGuard(Arc::clone(self)))
916 }
917 Err(active) => {
918 self.rejected.inc();
919 if !self.limited.swap(true, Ordering::Relaxed) {
920 warn!(
921 "refusing new connections: at the limit of {limit} connections \
922 ({active} active)"
923 );
924 }
925 None
926 }
927 }
928 }
929}
930
931struct ConnectionGuard(Arc<ConnectionLimiter>);
933
934impl Drop for ConnectionGuard {
935 fn drop(&mut self) {
936 self.0.active.fetch_sub(1, Ordering::SeqCst);
937 }
938}
939
940async fn reject<A>(conn: &mut FramedConn<A>, err: ErrorResponse) -> Result<(), io::Error>
947where
948 A: AsyncRead + AsyncWrite + Unpin,
949{
950 conn.send(err).await?;
951 conn.flush().await
952}
953
954async fn under_pre_resolved_timeout<F, T, E>(
960 timeout: Duration,
961 metrics: &ServerMetrics,
962 fut: F,
963) -> Result<T, E>
964where
965 F: Future<Output = Result<T, E>>,
966 E: From<io::Error>,
967{
968 if timeout.is_zero() {
969 return fut.await;
970 }
971 match tokio::time::timeout(timeout, fut).await {
972 Ok(result) => result,
973 Err(_) => {
974 metrics.pre_resolved_timeout().inc();
975 Err(io::Error::new(
976 io::ErrorKind::TimedOut,
977 "timed out before resolving a backend",
978 )
979 .into())
980 }
981 }
982}
983
984pub enum CancellationResolver {
985 Directory(PathBuf),
986 Static(String),
987}
988
989struct PgwireBalancer {
990 configs: ConfigSet,
992 tls: Option<ReloadingTlsConfig>,
993 internal_tls: Option<SslConnector>,
994 cancellation_resolver: Arc<CancellationResolver>,
995 resolver: Arc<BalancerResolver>,
996 metrics: ServerMetrics,
997 limiter: Arc<ConnectionLimiter>,
998 now: NowFn,
999}
1000
1001impl PgwireBalancer {
1002 async fn pre_resolve(
1007 conn: Connection,
1008 tls: Option<ReloadingTlsConfig>,
1009 resolver: &BalancerResolver,
1010 cancellation_resolver: Arc<CancellationResolver>,
1011 conn_uuid: Uuid,
1012 metrics: &ServerMetrics,
1013 ) -> Result<
1014 Option<(
1015 FramedConn<Connection>,
1016 ResolvedAddr,
1017 BTreeMap<String, String>,
1018 )>,
1019 anyhow::Error,
1020 > {
1021 let peer_addr = conn.peer_addr();
1022 let mut conn = Conn::Unencrypted(conn);
1023 loop {
1024 let message = decode_startup(&mut conn, MAX_STARTUP_FRAME_SIZE).await?;
1025 conn = match message {
1026 None => return Ok(None),
1030
1031 Some(FrontendStartupMessage::Startup {
1032 version,
1033 mut params,
1034 }) => {
1035 let mut conn = FramedConn::new(conn);
1036 let rejected = SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION;
1037 let peer_addr = match peer_addr {
1038 Ok(addr) => addr.ip(),
1039 Err(e) => {
1040 error!("Invalid peer_addr {:?}", e);
1041 reject(
1042 &mut conn,
1043 ErrorResponse::fatal(rejected, "invalid peer address"),
1044 )
1045 .await?;
1046 return Ok(None);
1047 }
1048 };
1049 debug!(
1050 %conn_uuid, %peer_addr,
1051 "starting new pgwire connection in balancer",
1052 );
1053 let prev = params.insert(CONN_UUID_KEY.to_string(), conn_uuid.to_string());
1054 if prev.is_some() {
1055 reject(
1056 &mut conn,
1057 ErrorResponse::fatal(
1058 rejected,
1059 format!("invalid parameter '{CONN_UUID_KEY}'"),
1060 ),
1061 )
1062 .await?;
1063 return Ok(None);
1064 }
1065
1066 let forwarded_for = params.insert(
1067 MZ_FORWARDED_FOR_KEY.to_string(),
1068 peer_addr.to_string().clone(),
1069 );
1070 if let Some(_) = forwarded_for {
1071 reject(
1072 &mut conn,
1073 ErrorResponse::fatal(
1074 rejected,
1075 format!("invalid parameter '{MZ_FORWARDED_FOR_KEY}'"),
1076 ),
1077 )
1078 .await?;
1079 return Ok(None);
1080 };
1081
1082 let Some(resolved) = Self::resolve_destination(
1083 &mut conn,
1084 version,
1085 ¶ms,
1086 resolver,
1087 tls.map(|tls| tls.mode),
1088 metrics,
1089 )
1090 .await?
1091 else {
1092 return Ok(None);
1093 };
1094 return Ok(Some((conn, resolved, params)));
1095 }
1096
1097 Some(FrontendStartupMessage::CancelRequest {
1098 conn_id,
1099 secret_key,
1100 }) => {
1101 spawn(|| "cancel request", async move {
1102 cancel_request(conn_id, secret_key, &cancellation_resolver).await;
1103 });
1104 return Ok(None);
1107 }
1108
1109 Some(FrontendStartupMessage::SslRequest) => match (conn, &tls) {
1110 (Conn::Unencrypted(mut conn), Some(tls)) => {
1111 conn.write_all(&[ACCEPT_SSL_ENCRYPTION]).await?;
1112 let mut ssl_stream = SslStream::new(Ssl::new(&tls.context.get())?, conn)?;
1113 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1114 let _ = ssl_stream.get_mut().shutdown().await;
1115 return Err(e.into());
1116 }
1117 Conn::Ssl(ssl_stream)
1118 }
1119 (mut conn, _) => {
1120 conn.write_all(&[REJECT_ENCRYPTION]).await?;
1121 conn
1122 }
1123 },
1124
1125 Some(FrontendStartupMessage::GssEncRequest) => {
1126 conn.write_all(&[REJECT_ENCRYPTION]).await?;
1127 conn
1128 }
1129 }
1130 }
1131 }
1132
1133 #[mz_ore::instrument(level = "debug")]
1134 async fn resolve_destination<'a, A>(
1138 conn: &'a mut FramedConn<A>,
1139 version: i32,
1140 params: &BTreeMap<String, String>,
1141 resolver: &BalancerResolver,
1142 tls_mode: Option<TlsMode>,
1143 metrics: &ServerMetrics,
1144 ) -> Result<Option<ResolvedAddr>, io::Error>
1145 where
1146 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1147 {
1148 if version != VERSION_3 {
1149 reject(
1150 conn,
1151 ErrorResponse::fatal(
1152 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1153 "server does not support the client's requested protocol version",
1154 ),
1155 )
1156 .await?;
1157 return Ok(None);
1158 }
1159
1160 let Some(user) = params.get("user") else {
1161 reject(
1162 conn,
1163 ErrorResponse::fatal(
1164 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1165 "user parameter required",
1166 ),
1167 )
1168 .await?;
1169 return Ok(None);
1170 };
1171
1172 if let Err(err) = conn.inner().ensure_tls_compatibility(&tls_mode) {
1173 reject(conn, err).await?;
1174 return Ok(None);
1175 }
1176
1177 let resolved = match resolver.resolve(conn, user, metrics).await {
1178 Ok(v) => v,
1179 Err(err) => {
1180 let sql_state = match &err {
1181 ResolveError::InvalidPassword => SqlState::INVALID_PASSWORD,
1182 ResolveError::Client(details) => {
1183 warn!("client-caused connection failure: {details:#}");
1184 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1185 }
1186 ResolveError::Upstream(details) => {
1187 warn!("upstream not available: {details:#}");
1188 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1189 }
1190 ResolveError::Internal(details) => {
1191 error!("resolving connection destination: {details:#}");
1192 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1193 }
1194 };
1195 reject(conn, ErrorResponse::fatal(sql_state, err.to_string())).await?;
1196 return Ok(None);
1197 }
1198 };
1199
1200 Ok(Some(resolved))
1201 }
1202
1203 async fn proxy<A>(
1205 mut conn: FramedConn<A>,
1206 resolved: ResolvedAddr,
1207 params: BTreeMap<String, String>,
1208 internal_tls: Option<&SslConnector>,
1209 metrics: &ServerMetrics,
1210 ) -> Result<(), io::Error>
1211 where
1212 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1213 {
1214 let _active_guard = resolved
1215 .tenant
1216 .as_ref()
1217 .map(|tenant| metrics.tenant_connections(tenant));
1218 let mut mz_stream = match Self::init_stream(
1219 &mut conn,
1220 resolved.addr,
1221 resolved.password,
1222 params,
1223 internal_tls,
1224 )
1225 .await
1226 {
1227 Ok(stream) => stream,
1228 Err(e) => {
1229 error!("failed to connect to upstream server: {e}");
1230 return reject(
1231 &mut conn,
1232 ErrorResponse::fatal(
1233 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1234 "upstream server not available",
1235 ),
1236 )
1237 .await;
1238 }
1239 };
1240
1241 let (mut client, unread) = conn.into_parts();
1245 if !unread.is_empty() {
1246 mz_stream.write_all(&unread).await?;
1247 }
1248 drop(unread);
1249
1250 let mut client_counter = CountingConn::new(&mut client);
1251
1252 let res = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1255 if let Some(tenant) = &resolved.tenant {
1256 metrics
1257 .tenant_connections_tx(tenant)
1258 .inc_by(u64::cast_from(client_counter.written));
1259 metrics
1260 .tenant_connections_rx(tenant)
1261 .inc_by(u64::cast_from(client_counter.read));
1262 }
1263 res?;
1264
1265 Ok(())
1266 }
1267
1268 #[mz_ore::instrument(level = "debug")]
1269 async fn init_stream<'a, A>(
1270 conn: &'a mut FramedConn<A>,
1271 envd_addr: SocketAddr,
1272 password: Option<String>,
1273 params: BTreeMap<String, String>,
1274 internal_tls: Option<&SslConnector>,
1275 ) -> Result<Conn<netio::Stream>, anyhow::Error>
1276 where
1277 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1278 {
1279 let mut mz_stream = netio::Stream::connect(envd_addr).await?;
1284 let mut buf = BytesMut::new();
1285
1286 let mut mz_stream = if let Some(internal_tls) = internal_tls {
1287 FrontendStartupMessage::SslRequest.encode(&mut buf)?;
1288 mz_stream.write_all(&buf).await?;
1289 buf.clear();
1290 let mut maybe_ssl_request_response = [0u8; 1];
1291 let nread =
1292 netio::read_exact_or_eof(&mut mz_stream, &mut maybe_ssl_request_response).await?;
1293 if nread == 1 && maybe_ssl_request_response == [ACCEPT_SSL_ENCRYPTION] {
1294 let mut ssl = internal_tls.configure()?.into_ssl(&envd_addr.to_string())?;
1296 ssl.set_connect_state();
1297 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1298 } else {
1299 Conn::Unencrypted(mz_stream)
1300 }
1301 } else {
1302 Conn::Unencrypted(mz_stream)
1303 };
1304
1305 let startup = FrontendStartupMessage::Startup {
1307 version: VERSION_3,
1308 params,
1309 };
1310 startup.encode(&mut buf)?;
1311 mz_stream.write_all(&buf).await?;
1312 let client_stream = conn.inner_mut();
1313
1314 if password.is_none() {
1330 return Ok(mz_stream);
1331 }
1332
1333 let mut maybe_auth_frame = [0; 1 + 4 + 4];
1337 let nread = netio::read_exact_or_eof(&mut mz_stream, &mut maybe_auth_frame).await?;
1338 const AUTH_PASSWORD_CLEARTEXT: [u8; 9] = [b'R', 0, 0, 0, 8, 0, 0, 0, 3];
1341 if nread == AUTH_PASSWORD_CLEARTEXT.len()
1342 && maybe_auth_frame == AUTH_PASSWORD_CLEARTEXT
1343 && password.is_some()
1344 {
1345 let Some(password) = password else {
1347 unreachable!("verified some above");
1348 };
1349 let password = FrontendMessage::Password { password };
1350 buf.clear();
1351 password.encode(&mut buf)?;
1352 mz_stream.write_all(&buf).await?;
1353 mz_stream.flush().await?;
1354 } else {
1355 client_stream.write_all(&maybe_auth_frame[0..nread]).await?;
1358 }
1359
1360 Ok(mz_stream)
1361 }
1362}
1363
1364impl mz_server_core::Server for PgwireBalancer {
1365 const NAME: &'static str = "pgwire_balancer";
1366
1367 fn handle_connection(
1368 &self,
1369 conn: Connection,
1370 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1371 ) -> mz_server_core::ConnectionHandler {
1372 let tls = self.tls.clone();
1373 let internal_tls = self.internal_tls.clone();
1374 let resolver = Arc::clone(&self.resolver);
1375 let inner_metrics = self.metrics.clone();
1376 let outer_metrics = self.metrics.clone();
1377 let limiter = Arc::clone(&self.limiter);
1378 let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1379 let cancellation_resolver = Arc::clone(&self.cancellation_resolver);
1380 let conn_uuid = epoch_to_uuid_v7(&(self.now)());
1381 conn.uuid_handle().set(conn_uuid);
1382 Box::pin(async move {
1383 let active_guard = outer_metrics.active_connections();
1386 let result: Result<(), anyhow::Error> = async move {
1387 let Some(_conn_guard) = limiter.acquire() else {
1388 return Ok(());
1389 };
1390 let pre_resolved = inner_metrics.pre_resolved_connections();
1391
1392 let destination = under_pre_resolved_timeout(
1396 pre_resolved_timeout,
1397 &inner_metrics,
1398 Box::pin(Self::pre_resolve(
1399 conn,
1400 tls,
1401 &resolver,
1402 cancellation_resolver,
1403 conn_uuid,
1404 &inner_metrics,
1405 )),
1406 )
1407 .await?;
1408
1409 let Some((conn, resolved, params)) = destination else {
1410 return Ok(());
1411 };
1412 drop(pre_resolved);
1413 if let Some(tenant) = &resolved.tenant {
1418 info!(
1419 target: NO_SENTRY_TARGET,
1420 %conn_uuid,
1421 client_addr = params.get(MZ_FORWARDED_FOR_KEY).map_or("", String::as_str),
1422 %tenant,
1423 authenticated = resolved.password.is_some(),
1424 "pgwire connection resolved",
1425 );
1426 }
1427
1428 PgwireBalancer::proxy(
1429 conn,
1430 resolved,
1431 params,
1432 internal_tls.as_ref(),
1433 &inner_metrics,
1434 )
1435 .await?;
1436 Ok(())
1437 }
1438 .await;
1439 drop(active_guard);
1440 outer_metrics.connection_status(result.is_ok()).inc();
1441 Ok(())
1442 })
1443 }
1444}
1445
1446struct CountingConn<C> {
1448 inner: C,
1449 read: usize,
1450 written: usize,
1451}
1452
1453impl<C> CountingConn<C> {
1454 fn new(inner: C) -> Self {
1455 CountingConn {
1456 inner,
1457 read: 0,
1458 written: 0,
1459 }
1460 }
1461}
1462
1463impl<C> AsyncRead for CountingConn<C>
1464where
1465 C: AsyncRead + Unpin,
1466{
1467 fn poll_read(
1468 self: Pin<&mut Self>,
1469 cx: &mut std::task::Context<'_>,
1470 buf: &mut io::ReadBuf<'_>,
1471 ) -> std::task::Poll<std::io::Result<()>> {
1472 let counter = self.get_mut();
1473 let pin = Pin::new(&mut counter.inner);
1474 let bytes = buf.filled().len();
1475 let poll = pin.poll_read(cx, buf);
1476 let bytes = buf.filled().len() - bytes;
1477 if let std::task::Poll::Ready(Ok(())) = poll {
1478 counter.read += bytes
1479 }
1480 poll
1481 }
1482}
1483
1484impl<C> AsyncWrite for CountingConn<C>
1485where
1486 C: AsyncWrite + Unpin,
1487{
1488 fn poll_write(
1489 self: Pin<&mut Self>,
1490 cx: &mut std::task::Context<'_>,
1491 buf: &[u8],
1492 ) -> std::task::Poll<Result<usize, std::io::Error>> {
1493 let counter = self.get_mut();
1494 let pin = Pin::new(&mut counter.inner);
1495 let poll = pin.poll_write(cx, buf);
1496 if let std::task::Poll::Ready(Ok(bytes)) = poll {
1497 counter.written += bytes
1498 }
1499 poll
1500 }
1501
1502 fn poll_flush(
1503 self: Pin<&mut Self>,
1504 cx: &mut std::task::Context<'_>,
1505 ) -> std::task::Poll<Result<(), std::io::Error>> {
1506 let counter = self.get_mut();
1507 let pin = Pin::new(&mut counter.inner);
1508 pin.poll_flush(cx)
1509 }
1510
1511 fn poll_shutdown(
1512 self: Pin<&mut Self>,
1513 cx: &mut std::task::Context<'_>,
1514 ) -> std::task::Poll<Result<(), std::io::Error>> {
1515 let counter = self.get_mut();
1516 let pin = Pin::new(&mut counter.inner);
1517 pin.poll_shutdown(cx)
1518 }
1519}
1520
1521async fn cancel_request(
1538 conn_id: u32,
1539 secret_key: u32,
1540 cancellation_resolver: &CancellationResolver,
1541) {
1542 let suffix = conn_id_org_uuid(conn_id);
1543 let contents = match cancellation_resolver {
1544 CancellationResolver::Directory(dir) => {
1545 let path = dir.join(&suffix);
1546 match std::fs::read_to_string(&path) {
1547 Ok(contents) => contents,
1548 Err(err) => {
1549 error!("could not read cancel file {path:?}: {err}");
1550 return;
1551 }
1552 }
1553 }
1554 CancellationResolver::Static(addr) => addr.to_owned(),
1555 };
1556 let mut all_ips = Vec::new();
1557 for addr in contents.lines() {
1558 let addr = addr.trim();
1559 if addr.is_empty() {
1560 continue;
1561 }
1562 match tokio::net::lookup_host(addr).await {
1563 Ok(ips) => all_ips.extend(ips),
1564 Err(err) => {
1565 error!("{addr} failed resolution: {err}");
1566 }
1567 }
1568 }
1569 let mut buf = BytesMut::with_capacity(16);
1570 let msg = FrontendStartupMessage::CancelRequest {
1571 conn_id,
1572 secret_key,
1573 };
1574 msg.encode(&mut buf).expect("must encode");
1575 let buf = buf.freeze();
1576 for ip in all_ips {
1577 debug!("cancelling {suffix} to {ip}");
1578 let buf = buf.clone();
1579 spawn(|| "cancel request for ip", async move {
1580 let send = async {
1581 let mut stream = TcpStream::connect(&ip).await?;
1582 stream.write_all(&buf).await?;
1583 stream.shutdown().await?;
1584 Ok::<_, io::Error>(())
1585 };
1586 if let Err(err) = send.await {
1587 error!("error mirroring cancel to {ip}: {err}");
1588 }
1589 });
1590 }
1591}
1592
1593async fn send_http_error(client_stream: &mut Box<dyn ClientStream>, status: &str, body: &str) {
1598 let response = format!(
1599 "HTTP/1.1 {status}\r\n\
1600 Content-Type: text/plain\r\n\
1601 Content-Length: {}\r\n\
1602 Connection: close\r\n\
1603 \r\n\
1604 {body}",
1605 body.len(),
1606 );
1607 let _ = client_stream.write_all(response.as_bytes()).await;
1608 let _ = client_stream.shutdown().await;
1609}
1610
1611struct HttpsBalancer {
1612 resolver: Arc<TenantDnsResolver>,
1613 tls: Option<ReloadingSslContext>,
1614 resolve_template: Arc<str>,
1615 port: u16,
1616 metrics: Arc<ServerMetrics>,
1617 limiter: Arc<ConnectionLimiter>,
1618 configs: ConfigSet,
1619 internal_tls: Option<SslConnector>,
1620}
1621
1622impl HttpsBalancer {
1623 async fn pre_resolve(
1625 conn: Connection,
1626 tls_context: Option<ReloadingSslContext>,
1627 resolver: &TenantDnsResolver,
1628 resolve_template: &str,
1629 port: u16,
1630 ) -> Result<(Box<dyn ClientStream>, ResolvedAddr, SocketAddr), anyhow::Error> {
1631 let peer_addr = conn.peer_addr().context("fetching peer addr")?;
1632 let (client_stream, servername): (Box<dyn ClientStream>, Option<String>) = match tls_context
1633 {
1634 Some(tls_context) => {
1635 let mut ssl_stream = SslStream::new(Ssl::new(&tls_context.get())?, conn)?;
1636 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1637 let _ = ssl_stream.get_mut().shutdown().await;
1638 return Err(e.into());
1639 }
1640 let servername: Option<String> =
1641 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1642 match sn.split_once('.') {
1643 Some((left, _right)) => left,
1644 None => sn,
1645 }
1646 .into()
1647 });
1648 debug!("Found sni servername: {servername:?} (https)");
1649 (Box::new(ssl_stream), servername)
1650 }
1651 _ => (Box::new(conn), None),
1652 };
1653 let resolved =
1654 Self::resolve(resolver, resolve_template, port, servername.as_deref()).await?;
1655 Ok((client_stream, resolved, peer_addr))
1656 }
1657
1658 async fn resolve(
1659 resolver: &TenantDnsResolver,
1660 resolve_template: &str,
1661 port: u16,
1662 servername: Option<&str>,
1663 ) -> Result<ResolvedAddr, anyhow::Error> {
1664 let (addr, tenant) = match servername {
1665 Some(sni) => resolver.resolve_sni(resolve_template, port, sni).await?,
1666 None => {
1667 debug!("https hostname (no SNI): {}:{}", resolve_template, port);
1670 resolver.resolve(resolve_template, port).await?
1671 }
1672 };
1673
1674 Ok(ResolvedAddr {
1675 addr,
1676 password: None,
1677 tenant,
1678 })
1679 }
1680}
1681
1682fn extract_tenant_from_cname(cname: &str) -> Option<String> {
1695 let mut parts = cname.split('.');
1696 let _service = parts.next();
1697 let Some(namespace) = parts.next() else {
1698 return None;
1699 };
1700 let Some((_, namespace)) = namespace.split_once('-') else {
1702 return None;
1703 };
1704 let Some((tenant, _)) = namespace.rsplit_once('-') else {
1706 return None;
1707 };
1708 let Ok(tenant) = Uuid::parse_str(tenant) else {
1711 error!("cname tenant not a uuid: {tenant}");
1712 return None;
1713 };
1714 Some(tenant.to_string())
1715}
1716
1717fn strip_ipv6_brackets(host: &str) -> &str {
1722 host.strip_prefix('[')
1723 .and_then(|h| h.strip_suffix(']'))
1724 .unwrap_or(host)
1725}
1726
1727impl mz_server_core::Server for HttpsBalancer {
1728 const NAME: &'static str = "https_balancer";
1729
1730 fn handle_connection(
1732 &self,
1733 conn: Connection,
1734 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1735 ) -> mz_server_core::ConnectionHandler {
1736 let tls_context = self.tls.clone();
1737 let internal_tls = self.internal_tls.clone();
1738 let resolver = Arc::clone(&self.resolver);
1739 let resolve_template = Arc::clone(&self.resolve_template);
1740 let port = self.port;
1741 let inner_metrics = Arc::clone(&self.metrics);
1742 let outer_metrics = Arc::clone(&self.metrics);
1743 let limiter = Arc::clone(&self.limiter);
1744 let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1745 let inject_proxy_headers = INJECT_PROXY_PROTOCOL_HEADER_HTTP.get(&self.configs);
1746 Box::pin(async move {
1747 let active_guard = inner_metrics.active_connections();
1748 let result: Result<_, anyhow::Error> = Box::pin(async move {
1749 let Some(_conn_guard) = limiter.acquire() else {
1750 return Ok(());
1751 };
1752 let pre_resolved = inner_metrics.pre_resolved_connections();
1753
1754 let (mut client_stream, resolved, peer_addr) = under_pre_resolved_timeout(
1755 pre_resolved_timeout,
1756 &inner_metrics,
1757 Self::pre_resolve(conn, tls_context, &resolver, &resolve_template, port),
1758 )
1759 .await?;
1760 drop(pre_resolved);
1761 if let Some(tenant) = &resolved.tenant {
1764 info!(
1765 target: NO_SENTRY_TARGET,
1766 client_addr = %peer_addr.ip(),
1767 %tenant,
1768 authenticated = false,
1769 "https connection resolved",
1770 );
1771 }
1772 let inner_active_guard = resolved
1773 .tenant
1774 .as_ref()
1775 .map(|tenant| inner_metrics.tenant_connections(tenant));
1776 let mut mz_stream = match netio::Stream::connect(resolved.addr).await {
1777 Ok(stream) => stream,
1778 Err(e) => {
1779 error!("failed to connect to upstream server: {e}");
1780 send_http_error(
1781 &mut client_stream,
1782 "502 Bad Gateway",
1783 "upstream server not available",
1784 )
1785 .await;
1786 return Ok(());
1787 }
1788 };
1789
1790 if inject_proxy_headers {
1791 let addrs = ProxiedAddress::stream(peer_addr, resolved.addr);
1793 let header = ProxyHeader::with_address(addrs);
1794 let mut buf = [0u8; 1024];
1795 let len = header.encode_to_slice_v2(&mut buf)?;
1796 mz_stream.write_all(&buf[..len]).await?;
1797 }
1798
1799 let mut mz_stream = if let Some(internal_tls) = &internal_tls {
1800 let mut ssl = internal_tls
1802 .configure()?
1803 .into_ssl(&resolved.addr.to_string())?;
1804 ssl.set_connect_state();
1805 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1806 } else {
1807 Conn::Unencrypted(mz_stream)
1808 };
1809
1810 let mut client_counter = CountingConn::new(client_stream);
1811
1812 let _ = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1816 if let Some(tenant) = &resolved.tenant {
1817 inner_metrics
1818 .tenant_connections_tx(tenant)
1819 .inc_by(u64::cast_from(client_counter.written));
1820 inner_metrics
1821 .tenant_connections_rx(tenant)
1822 .inc_by(u64::cast_from(client_counter.read));
1823 }
1824 drop(inner_active_guard);
1825 Ok(())
1826 })
1827 .await;
1828 drop(active_guard);
1829 outer_metrics.connection_status(result.is_ok()).inc();
1830 if let Err(e) = result {
1831 debug!("connection error: {e}");
1832 }
1833 Ok(())
1834 })
1835 }
1836}
1837
1838#[derive(Debug)]
1841pub struct SniTemplate {
1842 pub template: String,
1843 pub port: u16,
1844}
1845
1846trait ClientStream: AsyncRead + AsyncWrite + Unpin + Send {}
1847impl<T: AsyncRead + AsyncWrite + Unpin + Send> ClientStream for T {}
1848
1849#[derive(Debug)]
1850pub enum BalancerResolver {
1851 Static(String),
1852 MultiTenant {
1853 dns: Arc<TenantDnsResolver>,
1854 frontegg: FronteggResolver,
1855 sni: Option<SniTemplate>,
1856 },
1857}
1858
1859#[derive(Debug, thiserror::Error)]
1865enum ResolveError {
1866 #[error("invalid password")]
1867 InvalidPassword,
1868 #[error("internal error")]
1873 Client(#[source] anyhow::Error),
1874 #[error("upstream server not available")]
1881 Upstream(#[source] anyhow::Error),
1882 #[error("internal error")]
1884 Internal(#[from] anyhow::Error),
1885}
1886
1887impl From<io::Error> for ResolveError {
1888 fn from(e: io::Error) -> Self {
1889 ResolveError::Internal(e.into())
1890 }
1891}
1892
1893impl BalancerResolver {
1894 pub fn shared_dns(&self) -> Option<Arc<TenantDnsResolver>> {
1897 match self {
1898 BalancerResolver::Static(_) => None,
1899 BalancerResolver::MultiTenant { dns, .. } => Some(Arc::clone(dns)),
1900 }
1901 }
1902
1903 async fn resolve<A>(
1904 &self,
1905 conn: &mut FramedConn<A>,
1906 user: &str,
1907 metrics: &ServerMetrics,
1908 ) -> Result<ResolvedAddr, ResolveError>
1909 where
1910 A: AsyncRead + AsyncWrite + Unpin,
1911 {
1912 match self {
1913 BalancerResolver::MultiTenant {
1914 dns: dns_resolver,
1915 frontegg:
1916 FronteggResolver {
1917 auth,
1918 addr_template,
1919 },
1920 sni: sni_resolver,
1921 } => {
1922 let servername = match conn.inner() {
1923 Conn::Ssl(ssl_stream) => {
1924 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1925 match sn.split_once('.') {
1926 Some((left, _right)) => left,
1927 None => sn,
1928 }
1929 })
1930 }
1931 Conn::Unencrypted(_) => None,
1932 };
1933 let has_sni = servername.is_some();
1934 let resolved_addr = match (servername, sni_resolver.as_ref()) {
1935 (Some(servername), Some(SniTemplate { template, port })) => {
1936 let (addr, tenant) = dns_resolver
1940 .resolve_sni(template, *port, servername)
1941 .await
1942 .map_err(ResolveError::Upstream)?;
1943 debug!("pgwire SNI resolved tenant: {:?}", tenant);
1944 ResolvedAddr {
1945 addr,
1946 password: None,
1947 tenant,
1948 }
1949 }
1950 _ => {
1951 conn.send(BackendMessage::AuthenticationCleartextPassword)
1952 .await?;
1953 conn.flush().await?;
1954 let password = match conn.recv().await? {
1955 Some(FrontendMessage::Password { password }) => password,
1956 _ => {
1957 return Err(ResolveError::Client(anyhow::anyhow!(
1958 "expected Password message"
1959 )));
1960 }
1961 };
1962
1963 let auth_response = auth.authenticate(user, &password, None).await;
1967 let auth_session = match auth_response {
1968 Ok((auth_session, _)) => auth_session,
1969 Err(e) => {
1970 warn!("pgwire connection failed authentication: {}", e);
1971 return Err(ResolveError::InvalidPassword);
1972 }
1973 };
1974
1975 let hostname_with_port =
1976 addr_template.replace("{}", &auth_session.tenant_id().to_string());
1977 let (hostname, port_str) = hostname_with_port
1978 .rsplit_once(':')
1979 .ok_or_else(|| anyhow::anyhow!("port required in addr_template"))?;
1980 let port: u16 = port_str.parse().with_context(|| {
1981 format!("invalid port in addr_template: {}", port_str)
1982 })?;
1983 let addr = dns_resolver
1987 .resolve_addr(hostname, port)
1988 .await
1989 .map_err(ResolveError::Upstream)?;
1990 let tenant = auth_session.tenant_id().to_string();
1991 debug!("Frontegg resolved tenant: {}", tenant);
1992 ResolvedAddr {
1993 addr,
1994 password: Some(password),
1995 tenant: Some(tenant),
1996 }
1997 }
1998 };
1999 metrics
2000 .tenant_pgwire_sni_count(
2001 resolved_addr.tenant.as_deref().unwrap_or("unknown"),
2002 has_sni,
2003 )
2004 .inc();
2005
2006 Ok(resolved_addr)
2007 }
2008 BalancerResolver::Static(addr) => {
2009 let Some(addr) = tokio::net::lookup_host(addr).await?.next() else {
2012 return Err(anyhow::anyhow!("{addr} did not resolve to any addresses").into());
2013 };
2014
2015 Ok(ResolvedAddr {
2016 addr,
2017 password: None,
2018 tenant: None,
2019 })
2020 }
2021 }
2022 }
2023}
2024
2025fn create_resolver() -> Result<TokioResolver, anyhow::Error> {
2033 let (config, mut opts) = read_system_conf().context("reading system DNS configuration")?;
2034 opts.cache_size = 0;
2035 opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
2037
2038 Ok(
2039 Resolver::builder_with_config(config, TokioRuntimeProvider::default())
2040 .with_options(opts)
2041 .build()?,
2042 )
2043}
2044
2045#[derive(Debug)]
2051pub struct TenantDnsResolver {
2052 resolver: TokioResolver,
2053}
2054
2055impl TenantDnsResolver {
2056 pub fn new() -> Result<Self, anyhow::Error> {
2059 Ok(Self {
2060 resolver: create_resolver()?,
2061 })
2062 }
2063
2064 async fn resolve_cname(&self, hostname: &str) -> Option<String> {
2066 match self.resolver.lookup(hostname, RecordType::CNAME).await {
2067 Ok(cname_response) => {
2068 if let Some(cname_record) = cname_response.answers().first() {
2069 if let RData::CNAME(cname_data) = &cname_record.data {
2070 let cname = cname_data.to_string();
2071 debug!("CNAME for {}: {}", hostname, cname);
2072 return Some(cname);
2073 }
2074 }
2075 None
2076 }
2077 Err(e) => {
2078 debug!("CNAME lookup failed for {}: {}", hostname, e);
2079 None
2080 }
2081 }
2082 }
2083
2084 async fn resolve_a(&self, hostname: &str) -> Result<LookupIp, anyhow::Error> {
2086 self.resolver
2087 .lookup_ip(hostname)
2088 .await
2089 .with_context(|| format!("resolving A records for {}", hostname))
2090 }
2091
2092 pub async fn resolve_sni(
2103 &self,
2104 template: &str,
2105 port: u16,
2106 servername: &str,
2107 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2108 let hostname = template.replace("{}", servername);
2109 debug!("SNI hostname: {}", hostname);
2110 self.resolve(&hostname, port).await
2111 }
2112
2113 async fn resolve_addr(&self, host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
2116 let host = strip_ipv6_brackets(host);
2117 if let Ok(ip) = host.parse::<IpAddr>() {
2121 return Ok(SocketAddr::new(ip, port));
2122 }
2123 Self::first_addr(self.resolve_a(host).await?, port)
2124 }
2125
2126 async fn resolve(
2130 &self,
2131 host: &str,
2132 port: u16,
2133 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2134 let host = strip_ipv6_brackets(host);
2135 if let Ok(ip) = host.parse::<IpAddr>() {
2137 return Ok((SocketAddr::new(ip, port), None));
2138 }
2139
2140 let (ips, tenant) = if let Some(cname) = self.resolve_cname(host).await {
2142 let tenant = extract_tenant_from_cname(&cname);
2143 (self.resolve_a(&cname).await?, tenant)
2144 } else {
2145 (self.resolve_a(host).await?, None)
2146 };
2147
2148 Ok((Self::first_addr(ips, port)?, tenant))
2149 }
2150
2151 fn first_addr(ips: LookupIp, port: u16) -> Result<SocketAddr, anyhow::Error> {
2153 ips.iter()
2154 .next()
2155 .map(|ip| SocketAddr::new(ip, port))
2156 .ok_or_else(|| anyhow::anyhow!("no A records found in DNS response"))
2157 }
2158}
2159
2160#[derive(Debug)]
2161pub struct FronteggResolver {
2162 pub auth: FronteggAuthentication,
2163 pub addr_template: String,
2164}
2165
2166#[derive(Debug)]
2167struct ResolvedAddr {
2168 addr: SocketAddr,
2169 password: Option<String>,
2170 tenant: Option<String>,
2171}
2172
2173#[cfg(test)]
2174mod tests {
2175 use mz_dyncfg::ConfigUpdates;
2176
2177 use super::*;
2178
2179 #[mz_ore::test]
2180 fn test_tenant() {
2181 let tests = vec![
2182 ("", None),
2183 (
2184 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2185 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2186 ),
2187 (
2188 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local.",
2191 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2192 ),
2193 (
2194 "service.something-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.ssvvcc.cloister.faraway",
2196 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2197 ),
2198 (
2199 "environmentd.environment-58cd23ffa4d74bd0ad85a6ff29cc86c3-0.svc.cluster.local",
2201 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2202 ),
2203 (
2204 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-1234.svc.cluster.local",
2206 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2207 ),
2208 (
2209 "environmentd.environment-58CD23FF-A4D7-4BD0-AD85-A6FF29CC86C3-0.svc.cluster.local",
2211 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2212 ),
2213 (
2214 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3.svc.cluster.local",
2216 None,
2217 ),
2218 (
2219 "environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2221 None,
2222 ),
2223 (
2224 "environmentd.environment-8cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2226 None,
2227 ),
2228 ];
2229 for (name, expect) in tests {
2230 let cname = extract_tenant_from_cname(name);
2231 assert_eq!(
2232 cname.as_deref(),
2233 expect,
2234 "{name} got {cname:?} expected {expect:?}"
2235 );
2236 }
2237 }
2238
2239 #[mz_ore::test]
2240 fn test_strip_ipv6_brackets() {
2241 assert_eq!(strip_ipv6_brackets("[::1]"), "::1");
2242 assert_eq!(strip_ipv6_brackets("[2001:db8::1]"), "2001:db8::1");
2243 assert_eq!(strip_ipv6_brackets("127.0.0.1"), "127.0.0.1");
2245 assert_eq!(strip_ipv6_brackets("host.example.com"), "host.example.com");
2246 assert_eq!(strip_ipv6_brackets("[unclosed"), "[unclosed");
2247 assert_eq!(strip_ipv6_brackets("unopened]"), "unopened]");
2248 }
2249
2250 #[mz_ore::test]
2251 fn test_connection_limiter() {
2252 let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2253 let set_max = |max: u32| {
2254 let mut updates = ConfigUpdates::default();
2255 updates.add(&MAX_CONNECTIONS, max);
2256 updates.apply(&configs);
2257 };
2258
2259 set_max(2);
2260 let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2261 let first = limiter.acquire().expect("under the limit");
2262 let second = limiter.acquire().expect("at the limit");
2263 assert!(limiter.acquire().is_none());
2264 assert_eq!(limiter.rejected.get(), 1);
2265
2266 drop(second);
2268 let _third = limiter.acquire().expect("capacity freed");
2269 drop(first);
2270
2271 set_max(1);
2274 assert!(limiter.acquire().is_none());
2275
2276 set_max(0);
2278 assert!(limiter.acquire().is_some());
2279 }
2280
2281 #[mz_ore::test(tokio::test)]
2284 #[cfg_attr(miri, ignore)] async fn test_dyncfgs_sync_from_launchdarkly() {
2286 mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
2287 dyncfgs::all_dyncfgs(ConfigSet::default()),
2288 &BUILD_INFO,
2289 |_| Ok(()),
2290 None,
2291 Duration::ZERO,
2292 None,
2293 |_, _| {},
2294 )
2295 .await
2296 .expect("every balancerd dyncfg converts to a LaunchDarkly flag");
2297 }
2298
2299 #[mz_ore::test]
2300 fn test_connection_watermarks() {
2301 let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2302 let set = |low: usize, high: usize, max: u32| {
2303 let mut updates = ConfigUpdates::default();
2304 updates.add(&CONNECTION_LOW_WATERMARK, low);
2305 updates.add(&CONNECTION_HIGH_WATERMARK, high);
2306 updates.add(&MAX_CONNECTIONS, max);
2307 updates.apply(&configs);
2308 };
2309 let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2310 let _first = limiter.acquire().expect("under the limit");
2311 let second = limiter.acquire().expect("under the limit");
2312 let third = limiter.acquire().expect("under the limit");
2313
2314 for (low, high, max) in [
2316 (0, 0, 5),
2317 (1, 0, 5),
2318 (0, 3, 5),
2319 (3, 3, 5),
2320 (4, 3, 5),
2321 (1, 5, 5),
2322 (1, 3, 0),
2323 ] {
2324 set(low, high, max);
2325 limiter.evaluate_watermarks();
2326 assert!(
2327 limiter.accepting(),
2328 "watermarks low={low} high={high} max={max} must be ignored",
2329 );
2330 }
2331 assert_eq!(limiter.watermark_transitions.get(), 0);
2332
2333 set(2, 3, 5);
2334 limiter.evaluate_watermarks();
2335 assert!(!limiter.accepting(), "at the high watermark");
2336 assert_eq!(limiter.watermark_transitions.get(), 1);
2337
2338 drop(third);
2339 limiter.evaluate_watermarks();
2340 assert!(
2341 !limiter.accepting(),
2342 "between the watermarks the state is kept"
2343 );
2344 drop(second);
2345 limiter.evaluate_watermarks();
2346 assert!(
2347 limiter.accepting(),
2348 "below the low watermark readiness returns"
2349 );
2350 assert_eq!(limiter.watermark_transitions.get(), 2);
2351
2352 let _fourth = limiter.acquire().expect("under the limit");
2354 let _fifth = limiter.acquire().expect("under the limit");
2355 limiter.evaluate_watermarks();
2356 assert!(!limiter.accepting(), "at the high watermark again");
2357 set(0, 0, 5);
2358 limiter.evaluate_watermarks();
2359 assert!(
2360 limiter.accepting(),
2361 "disabling the watermarks restores readiness"
2362 );
2363 assert_eq!(limiter.watermark_transitions.get(), 4);
2364 }
2365
2366 #[mz_ore::test(tokio::test(start_paused = true))]
2368 async fn test_pre_resolved_timeout_zero_disables() {
2369 let metrics = ServerMetrics::new(
2370 ServerMetricsConfig::register_into(&MetricsRegistry::new()),
2371 "pgwire",
2372 );
2373
2374 let slow = async {
2376 tokio::time::sleep(Duration::from_secs(60 * 60)).await;
2377 Ok::<(), io::Error>(())
2378 };
2379 assert!(
2380 under_pre_resolved_timeout(Duration::ZERO, &metrics, slow)
2381 .await
2382 .is_ok()
2383 );
2384 assert_eq!(metrics.pre_resolved_timeout().get(), 0);
2385
2386 let stalled = std::future::pending::<Result<(), io::Error>>();
2388 let err = under_pre_resolved_timeout(Duration::from_secs(30), &metrics, stalled)
2389 .await
2390 .expect_err("a phase that never finishes must be cut short");
2391 assert_eq!(err.kind(), io::ErrorKind::TimedOut);
2392 assert_eq!(metrics.pre_resolved_timeout().get(), 1);
2393 }
2394}