1use std::collections::BTreeMap;
13use std::sync::Arc;
14use std::time::Duration;
15
16use anyhow::anyhow;
17use mz_dyncfg::ConfigSet;
18use mz_ore::url::SensitiveUrl;
19use tracing::warn;
20
21use mz_postgres_client::PostgresClientKnobs;
22use mz_postgres_client::metrics::PostgresClientMetrics;
23
24use crate::azure::{AzureBlob, AzureBlobConfig};
25use crate::file::{FileBlob, FileBlobConfig};
26#[cfg(feature = "foundationdb")]
27use crate::foundationdb::{FdbConsensus, FdbConsensusConfig};
28use crate::location::{Blob, Consensus, Determinate, ExternalError};
29use crate::mem::{MemBlob, MemBlobConfig, MemConsensus};
30use crate::metrics::S3BlobMetrics;
31use crate::postgres::{PostgresConsensus, PostgresConsensusConfig};
32use crate::s3::{S3Blob, S3BlobConfig};
33
34pub fn all_dyn_configs(configs: ConfigSet) -> ConfigSet {
36 configs.add(&crate::postgres::USE_POSTGRES_TUNED_QUERIES)
37}
38
39#[derive(Debug, Clone)]
41pub enum BlobConfig {
42 File(FileBlobConfig),
44 S3(S3BlobConfig),
46 Mem(bool),
49 Azure(AzureBlobConfig),
51 #[cfg(feature = "turmoil")]
52 Turmoil(crate::turmoil::BlobConfig),
54}
55
56pub trait BlobKnobs: std::fmt::Debug + Send + Sync {
58 fn operation_timeout(&self) -> Duration;
60 fn operation_attempt_timeout(&self) -> Duration;
62 fn connect_timeout(&self) -> Duration;
64 fn read_timeout(&self) -> Duration;
66 fn is_cc_active(&self) -> bool;
68}
69
70impl BlobConfig {
71 pub async fn open(self) -> Result<Arc<dyn Blob>, ExternalError> {
73 match self {
74 BlobConfig::File(config) => Ok(Arc::new(FileBlob::open(config).await?)),
75 BlobConfig::S3(config) => Ok(Arc::new(S3Blob::open(config).await?)),
76 BlobConfig::Azure(config) => Ok(Arc::new(AzureBlob::open(config).await?)),
77 BlobConfig::Mem(tombstone) => {
78 Ok(Arc::new(MemBlob::open(MemBlobConfig::new(tombstone))))
79 }
80 #[cfg(feature = "turmoil")]
81 BlobConfig::Turmoil(config) => Ok(Arc::new(crate::turmoil::TurmoilBlob::open(config))),
82 }
83 }
84
85 pub async fn try_from(
87 url: &SensitiveUrl,
88 knobs: Box<dyn BlobKnobs>,
89 metrics: S3BlobMetrics,
90 ) -> Result<Self, ExternalError> {
91 let mut query_params = url.query_pairs().collect::<BTreeMap<_, _>>();
92
93 let config = match url.scheme() {
94 "file" => {
95 let mut config = FileBlobConfig::from(url.path());
96 if query_params.remove("tombstone").is_some() {
97 config.tombstone = true;
98 }
99 Ok(BlobConfig::File(config))
100 }
101 "s3" => {
102 let bucket = url
103 .host()
104 .ok_or_else(|| anyhow!("missing bucket: {}", url))?
105 .to_string();
106 let prefix = url
107 .path()
108 .strip_prefix('/')
109 .unwrap_or_else(|| url.path())
110 .to_string();
111 let role_arn = query_params.remove("role_arn").map(|x| x.into_owned());
112 let endpoint = query_params.remove("endpoint").map(|x| x.into_owned());
113 let region = query_params.remove("region").map(|x| x.into_owned());
114
115 let credentials = match url.password() {
116 None => None,
117 Some(password) => Some((
118 String::from_utf8_lossy(&urlencoding::decode_binary(
119 url.username().as_bytes(),
120 ))
121 .into_owned(),
122 String::from_utf8_lossy(&urlencoding::decode_binary(password.as_bytes()))
123 .into_owned(),
124 )),
125 };
126
127 let config = S3BlobConfig::new(
128 bucket,
129 prefix,
130 role_arn,
131 endpoint,
132 region,
133 credentials,
134 knobs,
135 metrics,
136 )
137 .await?;
138
139 Ok(BlobConfig::S3(config))
140 }
141 "mem" => {
142 if !cfg!(debug_assertions) {
143 warn!("persist unexpectedly using in-mem blob in a release binary");
144 }
145 let tombstone = match query_params.remove("tombstone").as_deref() {
146 None | Some("true") => true,
147 Some("false") => false,
148 Some(other) => Err(Determinate::new(anyhow!(
149 "invalid tombstone param value: {other}"
150 )))?,
151 };
152 query_params.clear();
153 Ok(BlobConfig::Mem(tombstone))
154 }
155 "http" | "https" => match url
156 .host()
157 .ok_or_else(|| anyhow!("missing protocol: {}", url))?
158 .to_string()
159 .split_once('.')
160 {
161 Some((account, root))
163 if account == "devstoreaccount1" || root == "blob.core.windows.net" =>
164 {
165 if let Some(container) = url
166 .path_segments()
167 .expect("azure blob storage container")
168 .next()
169 {
170 query_params.clear();
171 Ok(BlobConfig::Azure(AzureBlobConfig::new(
172 account.to_string(),
173 container.to_string(),
174 "".to_string(),
178 metrics,
179 url.clone().into_redacted(),
180 knobs,
181 )?))
182 } else {
183 Err(anyhow!("unknown persist blob scheme: {}", url))
184 }
185 }
186 _ => Err(anyhow!("unknown persist blob scheme: {}", url)),
187 },
188 #[cfg(feature = "turmoil")]
189 "turmoil" => {
190 let cfg = crate::turmoil::BlobConfig::new(url);
191 Ok(BlobConfig::Turmoil(cfg))
192 }
193 p => Err(anyhow!("unknown persist blob scheme {}: {}", p, url)),
194 }?;
195
196 if !query_params.is_empty() {
197 return Err(ExternalError::from(anyhow!(
198 "unknown blob location params {}: {}",
199 query_params
200 .keys()
201 .map(|x| x.as_ref())
202 .collect::<Vec<_>>()
203 .join(" "),
204 url,
205 )));
206 }
207
208 Ok(config)
209 }
210}
211
212#[derive(Debug, Clone)]
214pub enum ConsensusConfig {
215 #[cfg(feature = "foundationdb")]
216 FoundationDB(FdbConsensusConfig),
218 Postgres(PostgresConsensusConfig),
220 Mem,
222 #[cfg(feature = "turmoil")]
223 Turmoil(crate::turmoil::ConsensusConfig),
225}
226
227impl ConsensusConfig {
228 pub async fn open(self) -> Result<Arc<dyn Consensus>, ExternalError> {
230 match self {
231 #[cfg(feature = "foundationdb")]
232 ConsensusConfig::FoundationDB(config) => {
233 Ok(Arc::new(FdbConsensus::open(config).await?))
234 }
235 ConsensusConfig::Postgres(config) => {
236 Ok(Arc::new(PostgresConsensus::open(config).await?))
237 }
238 ConsensusConfig::Mem => Ok(Arc::new(MemConsensus::default())),
239 #[cfg(feature = "turmoil")]
240 ConsensusConfig::Turmoil(config) => {
241 Ok(Arc::new(crate::turmoil::TurmoilConsensus::open(config)))
242 }
243 }
244 }
245
246 pub fn try_from(
248 url: &SensitiveUrl,
249 knobs: Box<dyn PostgresClientKnobs>,
250 metrics: PostgresClientMetrics,
251 dyncfg: Arc<ConfigSet>,
252 ) -> Result<Self, ExternalError> {
253 let config = match url.scheme() {
254 #[cfg(feature = "foundationdb")]
255 "foundationdb" => Ok(ConsensusConfig::FoundationDB(FdbConsensusConfig::new(
256 url.clone(),
257 )?)),
258 "postgres" | "postgresql" => Ok(ConsensusConfig::Postgres(
259 PostgresConsensusConfig::new(url, knobs, metrics, dyncfg)?,
260 )),
261 "mem" => {
262 if !cfg!(debug_assertions) {
263 warn!("persist unexpectedly using in-mem consensus in a release binary");
264 }
265 Ok(ConsensusConfig::Mem)
266 }
267 #[cfg(feature = "turmoil")]
268 "turmoil" => {
269 let cfg = crate::turmoil::ConsensusConfig::new(url);
270 Ok(ConsensusConfig::Turmoil(cfg))
271 }
272 p => Err(anyhow!("unknown persist consensus scheme {}: {}", p, url)),
273 }?;
274 Ok(config)
275 }
276}