1use std::fmt::Debug;
19use std::sync::Arc;
20
21use log::debug;
22
23use super::GOOSEFS_SCHEME;
24use super::config::GoosefsConfig;
25use super::core::GoosefsCore;
26use super::deleter::GoosefsDeleter;
27use super::lister::GoosefsLister;
28use super::reader::*;
29use super::writer::GoosefsWriter;
30use super::writer::GoosefsWriters;
31use opendal_core::raw::*;
32use opendal_core::*;
33
34#[doc = include_str!("docs.md")]
36#[derive(Default)]
37pub struct GoosefsBuilder {
38 pub(super) config: GoosefsConfig,
39}
40
41impl Debug for GoosefsBuilder {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("GoosefsBuilder")
44 .field("config", &self.config)
45 .finish_non_exhaustive()
46 }
47}
48
49impl GoosefsBuilder {
50 pub fn root(mut self, root: &str) -> Self {
54 self.config.root = if root.is_empty() {
55 None
56 } else {
57 Some(root.to_string())
58 };
59 self
60 }
61
62 pub fn master_addr(mut self, addr: &str) -> Self {
75 if !addr.is_empty() {
76 self.config.master_addr = Some(addr.to_string());
77 }
78 self
79 }
80
81 pub fn block_size(mut self, size: u64) -> Self {
83 self.config.block_size = Some(size);
84 self
85 }
86
87 pub fn chunk_size(mut self, size: u64) -> Self {
89 self.config.chunk_size = Some(size);
90 self
91 }
92
93 pub fn write_type(mut self, wt: &str) -> Self {
97 if !wt.is_empty() {
98 self.config.write_type = Some(wt.to_string());
99 }
100 self
101 }
102
103 pub fn auth_type(mut self, auth_type: &str) -> Self {
109 if !auth_type.is_empty() {
110 self.config.auth_type = Some(auth_type.to_string());
111 }
112 self
113 }
114
115 pub fn auth_username(mut self, username: &str) -> Self {
120 if !username.is_empty() {
121 self.config.auth_username = Some(username.to_string());
122 }
123 self
124 }
125}
126
127impl Builder for GoosefsBuilder {
128 type Config = GoosefsConfig;
129
130 fn build(self) -> Result<impl Service> {
132 debug!("GoosefsBuilder::build started: {:?}", self);
133
134 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
135 debug!("GoosefsBuilder use root {}", root);
136
137 let mut goosefs_config = goosefs_sdk::config::GoosefsConfig::from_properties_auto()
162 .map_err(|e| {
163 Error::new(
164 ErrorKind::ConfigInvalid,
165 format!("failed to auto-load goosefs config: {e}"),
166 )
167 .with_operation("Builder::build")
168 .with_context("service", GOOSEFS_SCHEME)
169 })?;
170
171 goosefs_config.root = root.clone();
173
174 if let Some(ref master_addr) = self.config.master_addr {
176 let addrs: Vec<String> = master_addr
177 .split(',')
178 .map(|s| s.trim().to_string())
179 .filter(|s| !s.is_empty())
180 .collect();
181
182 if addrs.is_empty() {
183 return Err(Error::new(
184 ErrorKind::ConfigInvalid,
185 "master_addr is empty after trimming",
186 )
187 .with_operation("Builder::build")
188 .with_context("service", GOOSEFS_SCHEME));
189 }
190
191 if addrs.len() == 1 {
192 goosefs_config.master_addr = addrs[0].clone();
193 goosefs_config.master_addrs = Vec::new();
194 } else {
195 goosefs_config.master_addr = addrs[0].clone();
196 goosefs_config.master_addrs = addrs;
197 }
198 }
199
200 if goosefs_config.master_addr.is_empty() && goosefs_config.master_addrs.is_empty() {
204 return Err(Error::new(
205 ErrorKind::ConfigInvalid,
206 "master_addr is not configured: set it via GoosefsBuilder::master_addr(...), \
207 the `master_addr` config key, the GOOSEFS_MASTER_ADDR env var, \
208 or `goosefs.master.hostname`/`goosefs.master.rpc.addresses` in goosefs-site.properties",
209 )
210 .with_operation("Builder::build")
211 .with_context("service", GOOSEFS_SCHEME));
212 }
213 debug!(
214 "GoosefsBuilder use master_addr {} (addrs={:?})",
215 goosefs_config.master_addr, goosefs_config.master_addrs
216 );
217
218 if let Some(block_size) = self.config.block_size {
219 goosefs_config.block_size = block_size;
220 }
221 if let Some(chunk_size) = self.config.chunk_size {
222 goosefs_config.chunk_size = chunk_size;
223 }
224
225 if let Some(ref wt) = self.config.write_type {
232 let wt_i32 = match wt.to_lowercase().as_str() {
233 "must_cache" => 1,
234 "try_cache" => 2,
235 "cache_through" => 3,
236 "through" => 4,
237 "async_through" => 5,
238 _ => 1, };
240 goosefs_config.write_type = Some(wt_i32);
241 }
242
243 if let Some(ref auth_type_str) = self.config.auth_type {
245 goosefs_config = goosefs_config
246 .with_auth_type_str(auth_type_str)
247 .map_err(|e| {
248 Error::new(
249 ErrorKind::ConfigInvalid,
250 format!("invalid auth_type: {}", e),
251 )
252 .with_operation("Builder::build")
253 .with_context("service", GOOSEFS_SCHEME)
254 })?;
255 }
256
257 if let Some(ref auth_username) = self.config.auth_username {
258 goosefs_config = goosefs_config.with_auth_username(auth_username);
259 }
260
261 goosefs_config.validate().map_err(|e| {
263 Error::new(
264 ErrorKind::ConfigInvalid,
265 format!("invalid goosefs config: {e}"),
266 )
267 .with_operation("Builder::build")
268 .with_context("service", GOOSEFS_SCHEME)
269 })?;
270
271 Ok(GoosefsBackend {
272 core: Arc::new(GoosefsCore::new(
273 ServiceInfo::new(GOOSEFS_SCHEME, &root, ""),
274 Capability {
275 stat: true,
276 read: true,
277 write: true,
278 write_can_multi: true,
279 write_with_if_not_exists: true,
284 create_dir: true,
285 delete: true,
286 list: true,
287 rename: true,
288 rename_with_if_not_exists: true,
289 shared: true,
290 ..Default::default()
291 },
292 root,
293 goosefs_config,
294 )),
295 })
296 }
297}
298
299#[derive(Debug, Clone)]
300pub struct GoosefsBackend {
301 pub(crate) core: Arc<GoosefsCore>,
302}
303
304impl Service for GoosefsBackend {
305 type Reader = oio::StreamReader<GoosefsReader>;
306 type Writer = GoosefsWriters;
307 type Lister = oio::PageLister<GoosefsLister>;
308 type Deleter = oio::OneShotDeleter<GoosefsDeleter>;
309 type Copier = ();
310
311 fn info(&self) -> ServiceInfo {
312 self.core.info.clone()
313 }
314
315 fn capability(&self) -> Capability {
316 self.core.capability
317 }
318
319 async fn create_dir(
320 &self,
321 _ctx: &OperationContext,
322 path: &str,
323 _: OpCreateDir,
324 ) -> Result<RpCreateDir> {
325 self.core.create_dir(path).await?;
326 Ok(RpCreateDir::default())
327 }
328
329 async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
330 let file_info = self.core.get_status(path).await?;
331 Ok(RpStat::new(self.core.file_info_to_metadata(&file_info)))
332 }
333 fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
334 let output: oio::StreamReader<GoosefsReader> = {
335 Ok(oio::StreamReader::new(GoosefsReader::new(
336 self.clone(),
337 path,
338 args,
339 )))
340 }?;
341
342 Ok(output)
343 }
344
345 fn write(&self, _ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
346 let output: GoosefsWriters = {
347 let w = GoosefsWriter::new(self.core.clone(), args.clone(), path.to_string());
348 Ok(w)
349 }?;
350
351 Ok(output)
352 }
353
354 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
355 let output: oio::OneShotDeleter<GoosefsDeleter> = {
356 Ok(oio::OneShotDeleter::new(GoosefsDeleter::new(
357 self.core.clone(),
358 )))
359 }?;
360
361 Ok(output)
362 }
363
364 fn list(&self, _ctx: &OperationContext, path: &str, _args: OpList) -> Result<Self::Lister> {
365 let output: oio::PageLister<GoosefsLister> = {
366 let l = GoosefsLister::new(self.core.clone(), path);
367 Ok(oio::PageLister::new(l))
368 }?;
369
370 Ok(output)
371 }
372
373 fn copy(
374 &self,
375 _ctx: &OperationContext,
376 _from: &str,
377 _to: &str,
378 _args: OpCopy,
379 _opts: OpCopier,
380 ) -> Result<Self::Copier> {
381 Err(Error::new(
382 ErrorKind::Unsupported,
383 "operation is not supported",
384 ))
385 }
386
387 async fn rename(
388 &self,
389 _ctx: &OperationContext,
390 from: &str,
391 to: &str,
392 args: OpRename,
393 ) -> Result<RpRename> {
394 self.core.rename(from, to, args.if_not_exists()).await?;
395 Ok(RpRename::default())
396 }
397
398 async fn presign(
399 &self,
400 _ctx: &OperationContext,
401 _path: &str,
402 _args: OpPresign,
403 ) -> Result<RpPresign> {
404 Err(Error::new(
405 ErrorKind::Unsupported,
406 "operation is not supported",
407 ))
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_builder_build() {
417 let builder = GoosefsBuilder::default()
418 .root("/data")
419 .master_addr("127.0.0.1:9200")
420 .build();
421 assert!(builder.is_ok());
422 }
423
424 #[test]
425 fn test_builder_ha() {
426 let builder = GoosefsBuilder::default()
427 .root("/data")
428 .master_addr("10.0.0.1:9200,10.0.0.2:9200,10.0.0.3:9200")
429 .build();
430 assert!(builder.is_ok());
431 }
432
433 #[test]
439 fn test_builder_blank_master_addr_fails() {
440 let err = GoosefsBuilder::default()
441 .root("/data")
442 .master_addr(" , , ")
443 .build()
444 .expect_err("build must fail when master_addr is blank");
445 assert_eq!(err.kind(), ErrorKind::ConfigInvalid);
446 assert!(
447 err.to_string().contains("master_addr is empty"),
448 "unexpected error message: {err}"
449 );
450 }
451
452 #[test]
453 fn test_capability_rename_with_if_not_exists() {
454 let backend = GoosefsBuilder::default()
455 .root("/data")
456 .master_addr("127.0.0.1:9200")
457 .build()
458 .expect("build");
459 let cap = backend.capability();
460 assert!(cap.write_with_if_not_exists);
461 assert!(
462 cap.rename_with_if_not_exists,
463 "rename_with_if_not_exists must be declared for Create publish"
464 );
465 }
466}