1use std::fmt::Debug;
19use std::sync::Arc;
20
21use bytes::Buf;
22use http::StatusCode;
23use log::debug;
24use mea::once::OnceCell;
25
26use super::WEBHDFS_SCHEME;
27use super::config::WebhdfsConfig;
28use super::core::WebhdfsCore;
29use super::core::parse_error;
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 _ => return Err(parse_error(resp)),
233 }
234 Ok(())
235 }
236}
237
238impl Service for WebhdfsBackend {
239 type Reader = oio::StreamReader<WebhdfsReader>;
240 type Writer = WebhdfsWriters;
241 type Lister = oio::PageLister<WebhdfsLister>;
242 type Deleter = oio::OneShotDeleter<WebhdfsDeleter>;
243 type Copier = ();
244
245 fn info(&self) -> ServiceInfo {
246 self.core.info.clone()
247 }
248
249 fn capability(&self) -> Capability {
250 self.core.capability
251 }
252
253 async fn create_dir(
255 &self,
256 ctx: &OperationContext,
257 path: &str,
258 _: OpCreateDir,
259 ) -> Result<RpCreateDir> {
260 let resp = self.core.webhdfs_create_dir(ctx, path).await?;
261
262 let status = resp.status();
263 match status {
268 StatusCode::CREATED | StatusCode::OK => {
269 let bs = resp.into_body();
270
271 let resp = serde_json::from_reader::<_, BooleanResp>(bs.reader())
272 .map_err(new_json_deserialize_error)?;
273
274 if resp.boolean {
275 Ok(RpCreateDir::default())
276 } else {
277 Err(Error::new(
278 ErrorKind::Unexpected,
279 "webhdfs create dir failed",
280 ))
281 }
282 }
283 _ => Err(parse_error(resp)),
284 }
285 }
286
287 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
288 self.core
290 .root_checker
291 .get_or_try_init(|| async { self.check_root(ctx).await })
292 .await?;
293
294 let resp = self.core.webhdfs_get_file_status(ctx, path).await?;
295 let status = resp.status();
296 match status {
297 StatusCode::OK => {
298 let bs = resp.into_body();
299
300 let file_status = serde_json::from_reader::<_, FileStatusWrapper>(bs.reader())
301 .map_err(new_json_deserialize_error)?
302 .file_status;
303
304 let meta = match file_status.ty {
305 FileStatusType::Directory => Metadata::new(EntryMode::DIR),
306 FileStatusType::File => Metadata::new(EntryMode::FILE)
307 .with_content_length(file_status.length)
308 .with_last_modified(Timestamp::from_millisecond(
309 file_status.modification_time,
310 )?),
311 };
312
313 Ok(RpStat::new(meta))
314 }
315
316 _ => Err(parse_error(resp)),
317 }
318 }
319 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
320 let output: oio::StreamReader<WebhdfsReader> = {
321 Ok(oio::StreamReader::new(WebhdfsReader::new(
322 self.clone(),
323 ctx.clone(),
324 path,
325 args,
326 )))
327 }?;
328
329 Ok(output)
330 }
331
332 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
333 let output: WebhdfsWriters = {
334 let w = WebhdfsWriter::new(
335 self.core.clone(),
336 ctx.clone(),
337 args.clone(),
338 path.to_string(),
339 );
340
341 let w = if args.append() {
342 WebhdfsWriters::Two(oio::AppendWriter::new(w))
343 } else {
344 WebhdfsWriters::One(oio::BlockWriter::new(
345 ctx.executor().clone(),
346 w,
347 args.concurrent(),
348 ))
349 };
350
351 Ok(w)
352 }?;
353
354 Ok(output)
355 }
356
357 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
358 let output: oio::OneShotDeleter<WebhdfsDeleter> = {
359 Ok(oio::OneShotDeleter::new(WebhdfsDeleter::new(
360 self.core.clone(),
361 ctx.clone(),
362 )))
363 }?;
364
365 Ok(output)
366 }
367
368 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
369 let output: oio::PageLister<WebhdfsLister> = {
370 if args.recursive() {
371 return Err(Error::new(
372 ErrorKind::Unsupported,
373 "WebHDFS doesn't support list with recursive",
374 ));
375 }
376
377 let path = path.trim_end_matches('/');
378 let l = WebhdfsLister::new(self.core.clone(), ctx.clone(), path);
379 Ok(oio::PageLister::new(l))
380 }?;
381
382 Ok(output)
383 }
384
385 fn copy(
386 &self,
387 _ctx: &OperationContext,
388 _from: &str,
389 _to: &str,
390 _args: OpCopy,
391 _opts: OpCopier,
392 ) -> Result<Self::Copier> {
393 Err(Error::new(
394 ErrorKind::Unsupported,
395 "operation is not supported",
396 ))
397 }
398
399 async fn rename(
400 &self,
401 _ctx: &OperationContext,
402 _from: &str,
403 _to: &str,
404 _args: OpRename,
405 ) -> Result<RpRename> {
406 Err(Error::new(
407 ErrorKind::Unsupported,
408 "operation is not supported",
409 ))
410 }
411
412 async fn presign(
413 &self,
414 _ctx: &OperationContext,
415 _path: &str,
416 _args: OpPresign,
417 ) -> Result<RpPresign> {
418 Err(Error::new(
419 ErrorKind::Unsupported,
420 "operation is not supported",
421 ))
422 }
423}