1use std::fmt::Debug;
19use std::sync::Arc;
20
21use asyncband::once::OnceCell;
22use bytes::Buf;
23use http::StatusCode;
24use log::debug;
25
26use super::WEBHDFS_SCHEME;
27use super::config::WebhdfsConfig;
28use super::core::parse_error;
29use super::core::{ErrorContext, WebhdfsCore};
30use super::deleter::WebhdfsDeleter;
31use super::lister::WebhdfsLister;
32use super::message::BooleanResp;
33use super::message::FileStatusType;
34use super::message::FileStatusWrapper;
35use super::reader::*;
36use super::writer::WebhdfsWriter;
37use super::writer::WebhdfsWriters;
38use opendal_core::raw::oio;
39use opendal_core::raw::*;
40use opendal_core::*;
41
42const WEBHDFS_DEFAULT_ENDPOINT: &str = "http://127.0.0.1:9870";
43
44#[doc = include_str!("docs.md")]
46#[derive(Debug, Default)]
47pub struct WebhdfsBuilder {
48 pub(super) config: WebhdfsConfig,
49}
50
51impl WebhdfsBuilder {
52 pub fn root(mut self, root: &str) -> Self {
60 self.config.root = if root.is_empty() {
61 None
62 } else {
63 Some(root.to_string())
64 };
65
66 self
67 }
68
69 pub fn endpoint(mut self, endpoint: &str) -> Self {
80 if !endpoint.is_empty() {
81 self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string());
83 }
84 self
85 }
86
87 pub fn user_name(mut self, user_name: &str) -> Self {
90 if !user_name.is_empty() {
91 self.config.user_name = Some(user_name.to_string());
92 }
93 self
94 }
95
96 pub fn delegation(mut self, delegation: &str) -> Self {
103 if !delegation.is_empty() {
104 self.config.delegation = Some(delegation.to_string());
105 }
106 self
107 }
108
109 pub fn disable_list_batch(mut self) -> Self {
116 self.config.disable_list_batch = true;
117 self
118 }
119
120 pub fn atomic_write_dir(mut self, dir: &str) -> Self {
126 self.config.atomic_write_dir = if dir.is_empty() {
127 None
128 } else {
129 Some(String::from(dir))
130 };
131 self
132 }
133}
134
135impl Builder for WebhdfsBuilder {
136 type Config = WebhdfsConfig;
137
138 fn build(self) -> Result<impl Service> {
146 debug!("start building backend: {self:?}");
147
148 let root = normalize_root(&self.config.root.unwrap_or_default());
149 debug!("backend use root {root}");
150
151 let endpoint = match self.config.endpoint {
153 Some(endpoint) => {
154 if endpoint.starts_with("http") {
155 endpoint
156 } else {
157 format!("http://{endpoint}")
158 }
159 }
160 None => WEBHDFS_DEFAULT_ENDPOINT.to_string(),
161 };
162 debug!("backend use endpoint {endpoint}");
163
164 let atomic_write_dir = self.config.atomic_write_dir;
165
166 let auth = self.config.delegation.map(|dt| format!("delegation={dt}"));
167
168 let info = ServiceInfo::new(WEBHDFS_SCHEME, &root, "");
169 let capability = Capability {
170 stat: true,
171
172 read: true,
173
174 write: true,
175 write_can_append: true,
176 write_can_multi: atomic_write_dir.is_some(),
177
178 create_dir: true,
179 delete: true,
180
181 list: true,
182
183 shared: true,
184
185 ..Default::default()
186 };
187
188 let accessor_info = info;
189 let core = Arc::new(WebhdfsCore {
190 info: accessor_info,
191 capability,
192 root,
193 endpoint,
194 user_name: self.config.user_name,
195 auth,
196 root_checker: OnceCell::new(),
197 atomic_write_dir,
198 disable_list_batch: self.config.disable_list_batch,
199 });
200
201 Ok(WebhdfsBackend { core })
202 }
203}
204
205#[derive(Debug, Clone)]
207pub struct WebhdfsBackend {
208 pub(crate) core: Arc<WebhdfsCore>,
209}
210
211impl WebhdfsBackend {
212 async fn check_root(&self, ctx: &OperationContext) -> Result<()> {
213 let resp = self.core.webhdfs_get_file_status(ctx, "/").await?;
214 match resp.status() {
215 StatusCode::OK => {
216 let bs = resp.into_body();
217
218 let file_status = serde_json::from_reader::<_, FileStatusWrapper>(bs.reader())
219 .map_err(new_json_deserialize_error)?
220 .file_status;
221
222 if file_status.ty == FileStatusType::File {
223 return Err(Error::new(
224 ErrorKind::ConfigInvalid,
225 "root path must be dir",
226 ));
227 }
228 }
229 StatusCode::NOT_FOUND => {
230 self.create_dir(ctx, "/", OpCreateDir::new()).await?;
231 }
232 _ => {
233 return Err(parse_error(
234 ErrorContext::new(ServiceOperation("GetFileStatus")),
235 resp,
236 ));
237 }
238 }
239 Ok(())
240 }
241}
242
243impl Service for WebhdfsBackend {
244 type Reader = oio::StreamReader<WebhdfsReader>;
245 type Writer = WebhdfsWriters;
246 type Lister = oio::PageLister<WebhdfsLister>;
247 type Deleter = oio::OneShotDeleter<WebhdfsDeleter>;
248 type Copier = ();
249 type Composer = ();
250
251 fn info(&self) -> ServiceInfo {
252 self.core.info.clone()
253 }
254
255 fn capability(&self) -> Capability {
256 self.core.capability
257 }
258
259 async fn create_dir(
261 &self,
262 ctx: &OperationContext,
263 path: &str,
264 _: OpCreateDir,
265 ) -> Result<RpCreateDir> {
266 let resp = self.core.webhdfs_create_dir(ctx, path).await?;
267
268 let status = resp.status();
269 match status {
274 StatusCode::CREATED | StatusCode::OK => {
275 let bs = resp.into_body();
276
277 let resp = serde_json::from_reader::<_, BooleanResp>(bs.reader())
278 .map_err(new_json_deserialize_error)?;
279
280 if resp.boolean {
281 Ok(RpCreateDir::default())
282 } else {
283 Err(Error::new(
284 ErrorKind::Unexpected,
285 "webhdfs create dir failed",
286 ))
287 }
288 }
289 _ => Err(parse_error(
290 ErrorContext::new(ServiceOperation("Mkdirs")),
291 resp,
292 )),
293 }
294 }
295
296 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
297 self.core
299 .root_checker
300 .get_or_try_init(|| async { self.check_root(ctx).await })
301 .await?;
302
303 let resp = self.core.webhdfs_get_file_status(ctx, path).await?;
304 let status = resp.status();
305 match status {
306 StatusCode::OK => {
307 let bs = resp.into_body();
308
309 let file_status = serde_json::from_reader::<_, FileStatusWrapper>(bs.reader())
310 .map_err(new_json_deserialize_error)?
311 .file_status;
312
313 let meta = match file_status.ty {
314 FileStatusType::Directory => MetadataBuilder::dir().build(),
315 FileStatusType::File => {
316 let mut metadata = MetadataBuilder::file(file_status.length);
317 metadata.last_modified(Timestamp::from_millisecond(
318 file_status.modification_time,
319 )?);
320 metadata.build()
321 }
322 };
323
324 Ok(RpStat::new(meta))
325 }
326
327 _ => Err(parse_error(
328 ErrorContext::new(ServiceOperation("GetFileStatus")),
329 resp,
330 )),
331 }
332 }
333 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
334 let output: oio::StreamReader<WebhdfsReader> = {
335 Ok(oio::StreamReader::new(WebhdfsReader::new(
336 self.clone(),
337 ctx.clone(),
338 path,
339 args,
340 )))
341 }?;
342
343 Ok(output)
344 }
345
346 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
347 let output: WebhdfsWriters = {
348 let w = WebhdfsWriter::new(
349 self.core.clone(),
350 ctx.clone(),
351 args.clone(),
352 path.to_string(),
353 );
354
355 let w = if args.append() {
356 WebhdfsWriters::Two(oio::AppendWriter::new(w))
357 } else {
358 WebhdfsWriters::One(oio::BlockWriter::new(
359 ctx.executor().clone(),
360 w,
361 args.concurrent(),
362 ))
363 };
364
365 Ok(w)
366 }?;
367
368 Ok(output)
369 }
370
371 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
372 let output: oio::OneShotDeleter<WebhdfsDeleter> = {
373 Ok(oio::OneShotDeleter::new(WebhdfsDeleter::new(
374 self.core.clone(),
375 ctx.clone(),
376 )))
377 }?;
378
379 Ok(output)
380 }
381
382 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
383 let output: oio::PageLister<WebhdfsLister> = {
384 if args.recursive() {
385 return Err(Error::new(
386 ErrorKind::Unsupported,
387 "WebHDFS doesn't support list with recursive",
388 ));
389 }
390
391 let path = path.trim_end_matches('/');
392 let l = WebhdfsLister::new(self.core.clone(), ctx.clone(), path);
393 Ok(oio::PageLister::new(l))
394 }?;
395
396 Ok(output)
397 }
398
399 fn copy(
400 &self,
401 _ctx: &OperationContext,
402 _from: &str,
403 _to: &str,
404 _args: OpCopy,
405 ) -> Result<Self::Copier> {
406 Err(Error::new(
407 ErrorKind::Unsupported,
408 "operation is not supported",
409 ))
410 }
411
412 async fn rename(
413 &self,
414 _ctx: &OperationContext,
415 _from: &str,
416 _to: &str,
417 _args: OpRename,
418 ) -> Result<RpRename> {
419 Err(Error::new(
420 ErrorKind::Unsupported,
421 "operation is not supported",
422 ))
423 }
424
425 async fn presign(
426 &self,
427 _ctx: &OperationContext,
428 _path: &str,
429 _args: OpPresign,
430 ) -> Result<RpPresign> {
431 Err(Error::new(
432 ErrorKind::Unsupported,
433 "operation is not supported",
434 ))
435 }
436}