iota_storage/object_store/
mod.rs1use std::{future::Future, sync::Arc};
6
7use anyhow::{Result, anyhow};
8use async_trait::async_trait;
9use bytes::Bytes;
10use futures::stream::BoxStream;
11use iota_config::object_storage_config::TRANSFER_STALL_TIMEOUT;
12use object_store::{DynObjectStore, ObjectMeta, ObjectStore, ObjectStoreExt, path::Path};
13pub mod http;
14pub mod util;
15
16#[async_trait]
17pub trait ObjectStoreGetExt: std::fmt::Display + Send + Sync + 'static {
18 async fn get_bytes(&self, src: &Path) -> Result<Bytes>;
20
21 async fn get_bytes_with_progress(
27 &self,
28 src: &Path,
29 on_bytes: &(dyn Fn(u64) + Send + Sync),
30 ) -> Result<Bytes> {
31 let bytes = self.get_bytes(src).await?;
32 on_bytes(bytes.len() as u64);
33 Ok(bytes)
34 }
35
36 async fn exists(&self, src: &Path) -> Result<bool>;
38
39 async fn object_size(&self, src: &Path) -> Result<u64>;
41}
42
43async fn with_stall_timeout<F: Future>(task_name: &str, src: &Path, fut: F) -> Result<F::Output> {
46 tokio::time::timeout(TRANSFER_STALL_TIMEOUT, fut)
47 .await
48 .map_err(|_| {
49 anyhow!("{task_name} for file {src} received nothing for {TRANSFER_STALL_TIMEOUT:?}")
50 })
51}
52
53pub(crate) async fn collect_get_result_with_progress(
56 result: object_store::GetResult,
57 src: &Path,
58 on_bytes: &(dyn Fn(u64) + Send + Sync),
59) -> Result<Bytes> {
60 use futures::StreamExt;
61 let mut buf = Vec::with_capacity(result.meta.size as usize);
62 let mut stream = result.into_stream();
63 while let Some(chunk) = with_stall_timeout("GET result stream", src, stream.next()).await? {
65 let chunk = chunk
66 .map_err(|e| anyhow!("Failed to stream GET result for file {src} with error: {e:?}"))?;
67 on_bytes(chunk.len() as u64);
68 buf.extend_from_slice(&chunk);
69 }
70 Ok(buf.into())
71}
72
73macro_rules! as_ref_get_ext_impl {
74 ($type:ty) => {
75 #[async_trait]
76 impl ObjectStoreGetExt for $type {
77 async fn get_bytes(&self, src: &Path) -> Result<Bytes> {
78 self.as_ref().get_bytes(src).await
79 }
80
81 async fn get_bytes_with_progress(
82 &self,
83 src: &Path,
84 on_bytes: &(dyn Fn(u64) + Send + Sync),
85 ) -> Result<Bytes> {
86 self.as_ref().get_bytes_with_progress(src, on_bytes).await
87 }
88
89 async fn exists(&self, src: &Path) -> Result<bool> {
90 self.as_ref().exists(src).await
91 }
92
93 async fn object_size(&self, src: &Path) -> Result<u64> {
94 self.as_ref().object_size(src).await
95 }
96 }
97 };
98}
99
100as_ref_get_ext_impl!(Arc<dyn ObjectStoreGetExt>);
101as_ref_get_ext_impl!(Box<dyn ObjectStoreGetExt>);
102
103macro_rules! as_ref_get_impl {
104 ($type:ty) => {
105 #[async_trait]
106 impl ObjectStoreGetExt for $type {
107 async fn get_bytes(&self, src: &Path) -> Result<Bytes> {
108 self.get_bytes_with_progress(src, &|_| {}).await
111 }
112
113 async fn get_bytes_with_progress(
114 &self,
115 src: &Path,
116 on_bytes: &(dyn Fn(u64) + Send + Sync),
117 ) -> Result<Bytes> {
118 let result = with_stall_timeout("GET request", src, self.get(src))
119 .await?
120 .map_err(|e| anyhow!("Failed to get file {src} with error: {e:?}"))?;
121 collect_get_result_with_progress(result, src, on_bytes).await
122 }
123
124 async fn exists(&self, src: &Path) -> Result<bool> {
125 match with_stall_timeout("HEAD request", src, self.head(src)).await? {
126 Ok(_) => Ok(true),
127 Err(object_store::Error::NotFound { .. }) => Ok(false),
128 Err(e) => Err(anyhow!(
129 "Failed to check if file {src} exists with error: {e:?}"
130 )),
131 }
132 }
133
134 async fn object_size(&self, src: &Path) -> Result<u64> {
135 with_stall_timeout("HEAD request", src, self.head(src))
136 .await?
137 .map(|meta| meta.size)
138 .map_err(|e| anyhow!("Failed to get size of file {src} with error: {e:?}"))
139 }
140 }
141 };
142}
143
144as_ref_get_impl!(Arc<dyn ObjectStore>);
145as_ref_get_impl!(Box<dyn ObjectStore>);
146
147#[async_trait]
148pub trait ObjectStoreListExt: Send + Sync + 'static {
149 async fn list_objects(
151 &self,
152 src: Option<&Path>,
153 ) -> BoxStream<'_, object_store::Result<ObjectMeta>>;
154}
155
156macro_rules! as_ref_list_ext_impl {
157 ($type:ty) => {
158 #[async_trait]
159 impl ObjectStoreListExt for $type {
160 async fn list_objects(
161 &self,
162 src: Option<&Path>,
163 ) -> BoxStream<'_, object_store::Result<ObjectMeta>> {
164 self.as_ref().list_objects(src).await
165 }
166 }
167 };
168}
169
170as_ref_list_ext_impl!(Arc<dyn ObjectStoreListExt>);
171as_ref_list_ext_impl!(Box<dyn ObjectStoreListExt>);
172
173#[async_trait]
174impl ObjectStoreListExt for Arc<DynObjectStore> {
175 async fn list_objects(
176 &self,
177 src: Option<&Path>,
178 ) -> BoxStream<'_, object_store::Result<ObjectMeta>> {
179 self.list(src)
180 }
181}
182
183#[async_trait]
184pub trait ObjectStorePutExt: Send + Sync + 'static {
185 async fn put_bytes(&self, src: &Path, bytes: Bytes) -> Result<()>;
187}
188
189macro_rules! as_ref_put_ext_impl {
190 ($type:ty) => {
191 #[async_trait]
192 impl ObjectStorePutExt for $type {
193 async fn put_bytes(&self, src: &Path, bytes: Bytes) -> Result<()> {
194 self.as_ref().put_bytes(src, bytes).await
195 }
196 }
197 };
198}
199
200as_ref_put_ext_impl!(Arc<dyn ObjectStorePutExt>);
201as_ref_put_ext_impl!(Box<dyn ObjectStorePutExt>);
202
203#[async_trait]
204impl ObjectStorePutExt for Arc<DynObjectStore> {
205 async fn put_bytes(&self, src: &Path, bytes: Bytes) -> Result<()> {
206 self.put(src, bytes.into()).await?;
207 Ok(())
208 }
209}
210
211#[async_trait]
212pub trait ObjectStoreDeleteExt: Send + Sync + 'static {
213 async fn delete_object(&self, src: &Path) -> Result<()>;
215}
216
217macro_rules! as_ref_delete_ext_impl {
218 ($type:ty) => {
219 #[async_trait]
220 impl ObjectStoreDeleteExt for $type {
221 async fn delete_object(&self, src: &Path) -> Result<()> {
222 self.as_ref().delete_object(src).await
223 }
224 }
225 };
226}
227
228as_ref_delete_ext_impl!(Arc<dyn ObjectStoreDeleteExt>);
229as_ref_delete_ext_impl!(Box<dyn ObjectStoreDeleteExt>);
230
231#[async_trait]
232
233impl ObjectStoreDeleteExt for Arc<DynObjectStore> {
234 async fn delete_object(&self, src: &Path) -> Result<()> {
235 self.delete(src).await?;
236 Ok(())
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use std::sync::{
243 Arc,
244 atomic::{AtomicU64, Ordering},
245 };
246
247 use bytes::Bytes;
248 use futures::StreamExt;
249 use object_store::{
250 Attributes, GetResult, GetResultPayload, ObjectMeta, ObjectStore, memory::InMemory,
251 path::Path,
252 };
253
254 use crate::object_store::{
255 ObjectStoreGetExt, ObjectStorePutExt, collect_get_result_with_progress,
256 };
257
258 #[tokio::test]
259 async fn test_dyn_object_store_get_bytes() -> anyhow::Result<()> {
260 let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
261 let path = Path::from("file1");
262 store
263 .put_bytes(&path, Bytes::from_static(b"Lorem ipsum"))
264 .await?;
265
266 assert_eq!(
267 store.get_bytes(&path).await?,
268 Bytes::from_static(b"Lorem ipsum")
269 );
270 assert!(store.get_bytes(&Path::from("missing")).await.is_err());
271 Ok(())
272 }
273
274 #[tokio::test(start_paused = true)]
278 async fn test_collect_get_result_with_progress_fails_on_a_stalled_stream() {
279 let src = Path::from("file1");
280 let payload = futures::stream::once(async {
281 Ok::<_, object_store::Error>(Bytes::from_static(b"Lorem"))
282 })
283 .chain(futures::stream::pending())
284 .boxed();
285 let result = GetResult {
286 range: 0..11,
287 payload: GetResultPayload::Stream(payload),
288 meta: ObjectMeta {
289 location: src.clone(),
290 last_modified: chrono::Utc::now(),
291 size: 11,
292 e_tag: None,
293 version: None,
294 },
295 attributes: Attributes::new(),
296 };
297
298 let received = AtomicU64::new(0);
299 let err = collect_get_result_with_progress(result, &src, &|n| {
300 received.fetch_add(n, Ordering::Relaxed);
301 })
302 .await
303 .unwrap_err();
304
305 assert!(err.to_string().contains("received nothing"), "{err}");
306 assert_eq!(received.load(Ordering::Relaxed), 5);
307 }
308
309 #[tokio::test]
310 async fn test_dyn_object_store_exists() -> anyhow::Result<()> {
311 let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
312 let path = Path::from("file1");
313 store
314 .put_bytes(&path, bytes::Bytes::from_static(b"Lorem ipsum"))
315 .await?;
316
317 assert!(store.exists(&path).await?);
318 assert!(!store.exists(&Path::from("missing")).await?);
319 Ok(())
320 }
321
322 #[tokio::test]
323 async fn test_dyn_object_store_object_size() -> anyhow::Result<()> {
324 let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
325 let path = Path::from("file1");
326 store
327 .put_bytes(&path, bytes::Bytes::from_static(b"Lorem ipsum"))
328 .await?;
329
330 assert_eq!(store.object_size(&path).await?, 11);
331 assert!(store.object_size(&Path::from("missing")).await.is_err());
332 Ok(())
333 }
334}