opendal 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +31 -36
- data/Cargo.toml +1 -1
- data/DEPENDENCIES.rust.tsv +25 -35
- data/README.md +1 -1
- data/core/CHANGELOG.md +22 -0
- data/core/Cargo.lock +145 -189
- data/core/Cargo.toml +95 -95
- data/core/DEPENDENCIES.rust.tsv +259 -290
- data/core/LICENSE +16 -0
- data/core/benches/vs_fs/Cargo.toml +1 -1
- data/core/benches/vs_s3/Cargo.toml +1 -1
- data/core/core/CHANGELOG.md +6031 -0
- data/core/core/Cargo.toml +1 -1
- data/core/core/LICENSE +16 -0
- data/core/core/src/docs/mod.rs +1 -1
- data/core/core/src/docs/upgrade.md +8 -0
- data/core/core/src/lib.rs +1 -3
- data/core/core/src/raw/futures_util.rs +26 -21
- data/core/core/src/raw/http_util/error.rs +9 -0
- data/core/core/src/raw/ops.rs +1 -0
- data/core/core/src/types/http_transport/mod.rs +30 -2
- data/core/core/src/types/http_transport/uri.rs +166 -0
- data/core/core/src/types/metadata.rs +1 -0
- data/core/core/src/types/mod.rs +2 -0
- data/core/http-transports/reqwest/Cargo.toml +1 -1
- data/core/http-transports/reqwest/LICENSE +16 -0
- data/core/http-transports/reqwest/src/lib.rs +46 -4
- data/core/layers/async-backtrace/Cargo.toml +2 -2
- data/core/layers/async-backtrace/LICENSE +16 -0
- data/core/layers/await-tree/Cargo.toml +2 -2
- data/core/layers/await-tree/LICENSE +16 -0
- data/core/layers/capability-check/Cargo.toml +2 -2
- data/core/layers/capability-check/LICENSE +16 -0
- data/core/layers/chaos/Cargo.toml +2 -2
- data/core/layers/chaos/LICENSE +16 -0
- data/core/layers/concurrent-limit/Cargo.toml +3 -3
- data/core/layers/concurrent-limit/LICENSE +16 -0
- data/core/layers/dtrace/Cargo.toml +2 -2
- data/core/layers/dtrace/LICENSE +16 -0
- data/core/layers/fastmetrics/Cargo.toml +3 -3
- data/core/layers/fastmetrics/LICENSE +16 -0
- data/core/layers/fastrace/Cargo.toml +2 -2
- data/core/layers/fastrace/LICENSE +16 -0
- data/core/layers/foyer/Cargo.toml +2 -2
- data/core/layers/foyer/LICENSE +16 -0
- data/core/layers/hotpath/Cargo.toml +2 -2
- data/core/layers/hotpath/LICENSE +16 -0
- data/core/layers/immutable-index/Cargo.toml +2 -2
- data/core/layers/immutable-index/LICENSE +16 -0
- data/core/layers/logging/Cargo.toml +2 -2
- data/core/layers/logging/LICENSE +16 -0
- data/core/layers/metrics/Cargo.toml +3 -3
- data/core/layers/metrics/LICENSE +16 -0
- data/core/layers/mime-guess/Cargo.toml +2 -2
- data/core/layers/mime-guess/LICENSE +16 -0
- data/core/layers/observe-metrics-common/Cargo.toml +1 -1
- data/core/layers/observe-metrics-common/LICENSE +16 -0
- data/core/layers/otelmetrics/Cargo.toml +3 -3
- data/core/layers/otelmetrics/LICENSE +16 -0
- data/core/layers/oteltrace/Cargo.toml +2 -2
- data/core/layers/oteltrace/LICENSE +16 -0
- data/core/layers/prometheus/Cargo.toml +3 -3
- data/core/layers/prometheus/LICENSE +16 -0
- data/core/layers/prometheus-client/Cargo.toml +3 -3
- data/core/layers/prometheus-client/LICENSE +16 -0
- data/core/layers/retry/Cargo.toml +4 -4
- data/core/layers/retry/LICENSE +16 -0
- data/core/layers/route/Cargo.toml +3 -3
- data/core/layers/route/LICENSE +16 -0
- data/core/layers/tail-cut/Cargo.toml +2 -2
- data/core/layers/tail-cut/LICENSE +16 -0
- data/core/layers/throttle/Cargo.toml +2 -2
- data/core/layers/throttle/LICENSE +16 -0
- data/core/layers/timeout/Cargo.toml +3 -3
- data/core/layers/timeout/LICENSE +16 -0
- data/core/layers/tracing/Cargo.toml +2 -2
- data/core/layers/tracing/LICENSE +16 -0
- data/core/services/aliyun-drive/Cargo.toml +2 -2
- data/core/services/aliyun-drive/LICENSE +16 -0
- data/core/services/alluxio/Cargo.toml +1 -1
- data/core/services/alluxio/LICENSE +16 -0
- data/core/services/azblob/Cargo.toml +2 -2
- data/core/services/azblob/LICENSE +16 -0
- data/core/services/azblob/src/backend.rs +21 -7
- data/core/services/azblob/src/config.rs +4 -1
- data/core/services/azdls/Cargo.toml +3 -3
- data/core/services/azdls/LICENSE +16 -0
- data/core/services/azfile/Cargo.toml +2 -2
- data/core/services/azfile/LICENSE +16 -0
- data/core/services/azure-common/Cargo.toml +1 -1
- data/core/services/azure-common/LICENSE +16 -0
- data/core/services/b2/Cargo.toml +2 -2
- data/core/services/b2/LICENSE +16 -0
- data/core/services/cacache/Cargo.toml +1 -1
- data/core/services/cacache/LICENSE +16 -0
- data/core/services/cloudflare-kv/Cargo.toml +1 -1
- data/core/services/cloudflare-kv/LICENSE +16 -0
- data/core/services/compfs/Cargo.toml +1 -1
- data/core/services/compfs/LICENSE +16 -0
- data/core/services/compfs/src/core.rs +27 -11
- data/core/services/cos/Cargo.toml +1 -1
- data/core/services/cos/LICENSE +16 -0
- data/core/services/d1/Cargo.toml +1 -1
- data/core/services/d1/LICENSE +16 -0
- data/core/services/dashmap/Cargo.toml +1 -1
- data/core/services/dashmap/LICENSE +16 -0
- data/core/services/dbfs/Cargo.toml +1 -1
- data/core/services/dbfs/LICENSE +16 -0
- data/core/services/dropbox/Cargo.toml +2 -2
- data/core/services/dropbox/LICENSE +16 -0
- data/core/services/etcd/Cargo.toml +2 -2
- data/core/services/etcd/LICENSE +16 -0
- data/core/services/etcd/src/core.rs +3 -3
- data/core/services/foundationdb/Cargo.toml +1 -1
- data/core/services/foundationdb/LICENSE +16 -0
- data/core/services/foyer/Cargo.toml +3 -3
- data/core/services/foyer/LICENSE +16 -0
- data/core/services/fs/Cargo.toml +1 -1
- data/core/services/fs/LICENSE +16 -0
- data/core/services/fs/src/core.rs +69 -10
- data/core/services/ftp/Cargo.toml +2 -2
- data/core/services/ftp/LICENSE +16 -0
- data/core/services/ftp/src/core.rs +1 -1
- data/core/services/ftp/src/reader.rs +1 -1
- data/core/services/ftp/src/writer.rs +1 -1
- data/core/services/gcs/Cargo.toml +3 -2
- data/core/services/gcs/LICENSE +16 -0
- data/core/services/gcs/src/composer.rs +11 -21
- data/core/services/gcs/src/core.rs +116 -2
- data/core/services/gcs-grpc/Cargo.toml +1 -1
- data/core/services/gcs-grpc/proto/google/storage/v2/storage.proto +17 -18
- data/core/services/gdrive/Cargo.toml +3 -2
- data/core/services/gdrive/LICENSE +16 -0
- data/core/services/gdrive/src/docs.md +2 -0
- data/core/services/gdrive/src/path_index.rs +109 -18
- data/core/services/ghac/Cargo.toml +2 -2
- data/core/services/ghac/LICENSE +16 -0
- data/core/services/github/Cargo.toml +1 -1
- data/core/services/github/LICENSE +16 -0
- data/core/services/goosefs/Cargo.toml +13 -4
- data/core/services/goosefs/LICENSE +16 -0
- data/core/services/goosefs/README.md +11 -0
- data/core/services/goosefs/src/backend.rs +376 -49
- data/core/services/goosefs/src/config.rs +18 -10
- data/core/services/goosefs/src/core.rs +1 -1
- data/core/services/goosefs/src/docs.md +72 -3
- data/core/services/goosefs/src/reader.rs +22 -89
- data/core/services/goosefs/tests/master_addr_resolution.rs +103 -0
- data/core/services/gridfs/Cargo.toml +2 -2
- data/core/services/gridfs/LICENSE +16 -0
- data/core/services/hdfs/Cargo.toml +1 -1
- data/core/services/hdfs/LICENSE +16 -0
- data/core/services/hdfs-native/Cargo.toml +1 -1
- data/core/services/hdfs-native/LICENSE +16 -0
- data/core/services/hf/Cargo.toml +3 -3
- data/core/services/hf/LICENSE +16 -0
- data/core/services/hf/src/backend.rs +77 -9
- data/core/services/hf/src/config.rs +58 -0
- data/core/services/hf/src/core.rs +695 -318
- data/core/services/hf/src/docs.md +29 -0
- data/core/services/hf/src/lister.rs +3 -3
- data/core/services/hf/src/reader.rs +128 -143
- data/core/services/http/Cargo.toml +3 -3
- data/core/services/http/LICENSE +16 -0
- data/core/services/ipfs/Cargo.toml +1 -1
- data/core/services/ipfs/LICENSE +16 -0
- data/core/services/ipmfs/Cargo.toml +1 -1
- data/core/services/ipmfs/LICENSE +16 -0
- data/core/services/koofr/Cargo.toml +2 -2
- data/core/services/koofr/LICENSE +16 -0
- data/core/services/lakefs/Cargo.toml +1 -1
- data/core/services/lakefs/LICENSE +16 -0
- data/core/services/memcached/Cargo.toml +2 -2
- data/core/services/memcached/LICENSE +16 -0
- data/core/services/memcached/src/core.rs +3 -3
- data/core/services/mini_moka/Cargo.toml +1 -1
- data/core/services/mini_moka/LICENSE +16 -0
- data/core/services/moka/Cargo.toml +1 -1
- data/core/services/moka/LICENSE +16 -0
- data/core/services/mongodb/Cargo.toml +2 -2
- data/core/services/mongodb/LICENSE +16 -0
- data/core/services/monoiofs/Cargo.toml +2 -2
- data/core/services/monoiofs/LICENSE +16 -0
- data/core/services/monoiofs/src/core.rs +27 -11
- data/core/services/mysql/Cargo.toml +2 -2
- data/core/services/mysql/LICENSE +16 -0
- data/core/services/obs/Cargo.toml +1 -1
- data/core/services/obs/LICENSE +16 -0
- data/core/services/onedrive/Cargo.toml +2 -2
- data/core/services/onedrive/LICENSE +16 -0
- data/core/services/opfs/Cargo.toml +1 -1
- data/core/services/opfs/LICENSE +16 -0
- data/core/services/oss/Cargo.toml +1 -1
- data/core/services/oss/LICENSE +16 -0
- data/core/services/pcloud/Cargo.toml +1 -1
- data/core/services/pcloud/LICENSE +16 -0
- data/core/services/persy/Cargo.toml +1 -1
- data/core/services/persy/LICENSE +16 -0
- data/core/services/postgresql/Cargo.toml +2 -2
- data/core/services/postgresql/LICENSE +16 -0
- data/core/services/redb/Cargo.toml +1 -1
- data/core/services/redb/LICENSE +16 -0
- data/core/services/redis/Cargo.toml +2 -2
- data/core/services/redis/LICENSE +16 -0
- data/core/services/redis/src/core.rs +1 -1
- data/core/services/rocksdb/Cargo.toml +1 -1
- data/core/services/rocksdb/LICENSE +16 -0
- data/core/services/s3/Cargo.toml +1 -1
- data/core/services/s3/LICENSE +16 -0
- data/core/services/seafile/Cargo.toml +4 -2
- data/core/services/seafile/LICENSE +16 -0
- data/core/services/seafile/src/backend.rs +2 -3
- data/core/services/seafile/src/core.rs +189 -86
- data/core/services/seafile/src/docs.md +2 -0
- data/core/services/sftp/Cargo.toml +2 -2
- data/core/services/sftp/LICENSE +16 -0
- data/core/services/sftp/src/core.rs +1 -1
- data/core/services/sftp/src/reader.rs +1 -1
- data/core/services/sled/Cargo.toml +1 -1
- data/core/services/sled/LICENSE +16 -0
- data/core/services/sqlite/Cargo.toml +2 -2
- data/core/services/sqlite/LICENSE +16 -0
- data/core/services/surrealdb/Cargo.toml +2 -2
- data/core/services/surrealdb/LICENSE +16 -0
- data/core/services/swift/Cargo.toml +1 -1
- data/core/services/swift/LICENSE +16 -0
- data/core/services/tikv/Cargo.toml +2 -2
- data/core/services/tikv/LICENSE +16 -0
- data/core/services/tos/Cargo.toml +1 -1
- data/core/services/tos/LICENSE +16 -0
- data/core/services/upyun/Cargo.toml +1 -1
- data/core/services/upyun/LICENSE +16 -0
- data/core/services/vercel-artifacts/Cargo.toml +1 -1
- data/core/services/vercel-artifacts/LICENSE +16 -0
- data/core/services/vercel-blob/Cargo.toml +1 -1
- data/core/services/vercel-blob/LICENSE +16 -0
- data/core/services/webdav/Cargo.toml +1 -2
- data/core/services/webdav/LICENSE +16 -0
- data/core/services/webhdfs/Cargo.toml +2 -2
- data/core/services/webhdfs/LICENSE +16 -0
- data/core/services/yandex-disk/Cargo.toml +1 -1
- data/core/services/yandex-disk/LICENSE +16 -0
- data/core/src/lib.rs +1 -3
- data/core/testkit/Cargo.toml +4 -4
- data/core/testkit/LICENSE +16 -0
- data/core/upgrade.md +8 -0
- metadata +5 -2
|
@@ -16,94 +16,25 @@
|
|
|
16
16
|
// under the License.
|
|
17
17
|
|
|
18
18
|
use super::backend::*;
|
|
19
|
-
use super::core::GoosefsCore;
|
|
20
19
|
use super::core::{ErrorContext, parse_error};
|
|
21
20
|
use goosefs_sdk::io::GoosefsFileReader as SdkReader;
|
|
22
21
|
use opendal_core::raw::*;
|
|
23
22
|
use opendal_core::*;
|
|
24
|
-
use std::sync::Arc;
|
|
25
23
|
|
|
26
24
|
pub struct GoosefsReadStream {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
range: BytesRange,
|
|
30
|
-
content_length: Option<u64>,
|
|
31
|
-
/// Lazily-opened SDK reader. `None` until the first `read()` call.
|
|
32
|
-
inner: Option<SdkReader>,
|
|
33
|
-
/// Terminal flag: once the underlying stream has returned `None`,
|
|
34
|
-
/// subsequent `read()` calls must keep returning an empty `Buffer`
|
|
35
|
-
/// without re-entering the SDK (which would try to open a fresh
|
|
36
|
-
/// stream and re-read the file).
|
|
25
|
+
inner: SdkReader,
|
|
26
|
+
// The SDK may reopen its stream after EOF; keep OpenDAL's EOF terminal.
|
|
37
27
|
done: bool,
|
|
38
28
|
}
|
|
39
29
|
|
|
40
|
-
impl GoosefsReadStream {
|
|
41
|
-
pub fn new(
|
|
42
|
-
core: Arc<GoosefsCore>,
|
|
43
|
-
path: String,
|
|
44
|
-
range: BytesRange,
|
|
45
|
-
content_length: Option<u64>,
|
|
46
|
-
) -> Self {
|
|
47
|
-
GoosefsReadStream {
|
|
48
|
-
core,
|
|
49
|
-
path,
|
|
50
|
-
range,
|
|
51
|
-
content_length,
|
|
52
|
-
inner: None,
|
|
53
|
-
done: false,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Open the underlying SDK reader, picking between the full-file
|
|
58
|
-
/// and ranged variants based on `self.range`.
|
|
59
|
-
async fn open(&self) -> Result<SdkReader> {
|
|
60
|
-
let offset = self.range.offset();
|
|
61
|
-
let size = self.range.size();
|
|
62
|
-
|
|
63
|
-
// Three cases:
|
|
64
|
-
// 1. No offset and no size → full-file stream.
|
|
65
|
-
// 2. Offset+size both set → ranged stream.
|
|
66
|
-
// 3. Offset set, size unbounded → resolve tail length via
|
|
67
|
-
// get_status and fall through to the ranged opener, so we
|
|
68
|
-
// still get per-block streaming (rather than buffering the
|
|
69
|
-
// whole tail in one call).
|
|
70
|
-
match (offset, size) {
|
|
71
|
-
(0, None) => self.core.open_reader(&self.path).await,
|
|
72
|
-
(off, Some(len)) => self.core.open_range_reader(&self.path, off, len).await,
|
|
73
|
-
(off, None) => {
|
|
74
|
-
let content_length = self.content_length.ok_or_else(|| {
|
|
75
|
-
Error::new(
|
|
76
|
-
ErrorKind::Unexpected,
|
|
77
|
-
"content length must be known for offset reads",
|
|
78
|
-
)
|
|
79
|
-
})?;
|
|
80
|
-
let len = content_length.saturating_sub(off);
|
|
81
|
-
if len == 0 {
|
|
82
|
-
// Empty tail — short-circuit with a zero-length
|
|
83
|
-
// ranged open so the very next `read_next_block`
|
|
84
|
-
// call returns `None` and we EOF cleanly.
|
|
85
|
-
self.core.open_range_reader(&self.path, off, 0).await
|
|
86
|
-
} else {
|
|
87
|
-
self.core.open_range_reader(&self.path, off, len).await
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
30
|
impl oio::ReadStream for GoosefsReadStream {
|
|
95
31
|
async fn read(&mut self) -> Result<Buffer> {
|
|
96
32
|
if self.done {
|
|
97
33
|
return Ok(Buffer::new());
|
|
98
34
|
}
|
|
99
35
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
self.inner = Some(self.open().await?);
|
|
103
|
-
}
|
|
104
|
-
let reader = self.inner.as_mut().expect("inner was just set");
|
|
105
|
-
|
|
106
|
-
match reader
|
|
36
|
+
match self
|
|
37
|
+
.inner
|
|
107
38
|
.read_next_block()
|
|
108
39
|
.await
|
|
109
40
|
.map_err(|err| parse_error(ErrorContext::new(ServiceOperation("ReadNextBlock")), err))?
|
|
@@ -137,25 +68,27 @@ impl oio::StreamRead for GoosefsReader {
|
|
|
137
68
|
let backend = &self.backend;
|
|
138
69
|
let path = self.path.as_str();
|
|
139
70
|
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
Some(
|
|
143
|
-
|
|
71
|
+
let inner = match (range.offset(), range.size()) {
|
|
72
|
+
(0, None) => backend.core.open_reader(path).await?,
|
|
73
|
+
(offset, Some(length)) => backend.core.open_range_reader(path, offset, length).await?,
|
|
74
|
+
(offset, None) => {
|
|
75
|
+
let file_info = backend.core.get_status(path).await?;
|
|
76
|
+
let content_length = backend
|
|
144
77
|
.core
|
|
145
78
|
.file_info_to_metadata(&file_info)?
|
|
146
|
-
.content_length()
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
79
|
+
.content_length();
|
|
80
|
+
backend
|
|
81
|
+
.core
|
|
82
|
+
.open_range_reader(path, offset, content_length.saturating_sub(offset))
|
|
83
|
+
.await?
|
|
84
|
+
}
|
|
150
85
|
};
|
|
151
|
-
let
|
|
152
|
-
let stream = GoosefsReadStream
|
|
153
|
-
backend.core.clone(),
|
|
154
|
-
path.to_string(),
|
|
155
|
-
range,
|
|
156
|
-
content_length,
|
|
157
|
-
);
|
|
86
|
+
let metadata = backend.core.file_info_to_metadata(inner.file_info())?;
|
|
87
|
+
let stream = GoosefsReadStream { inner, done: false };
|
|
158
88
|
|
|
159
|
-
Ok((
|
|
89
|
+
Ok((
|
|
90
|
+
RpRead::new(metadata),
|
|
91
|
+
Box::new(stream) as Box<dyn oio::ReadStreamDyn>,
|
|
92
|
+
))
|
|
160
93
|
}
|
|
161
94
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
|
|
18
|
+
//! End-to-end master-address resolution against a live GooseFS master.
|
|
19
|
+
//!
|
|
20
|
+
//! Requires a live GooseFS master. Skip unless `OPENDAL_GOOSEFS_MASTER_ADDR`
|
|
21
|
+
//! is set (same env the behavior CI fixture exports).
|
|
22
|
+
//!
|
|
23
|
+
//! The whole file is one test because it mutates process-global `GOOSEFS_*`
|
|
24
|
+
//! environment variables.
|
|
25
|
+
|
|
26
|
+
use std::time::Duration;
|
|
27
|
+
|
|
28
|
+
use opendal::Operator;
|
|
29
|
+
|
|
30
|
+
/// A dummy address from TEST-NET-1 (RFC 5737): guaranteed not to host a
|
|
31
|
+
/// GooseFS master.
|
|
32
|
+
const DUMMY_AUTHORITY: &str = "192.0.2.5:9999";
|
|
33
|
+
|
|
34
|
+
/// Bound for an operation expected to fail against [`DUMMY_AUTHORITY`]. The
|
|
35
|
+
/// SDK retries master inquiry for far longer than a test should wait, so a
|
|
36
|
+
/// timeout counts as "did not reach a master".
|
|
37
|
+
const DIAL_FAILURE_TIMEOUT: Duration = Duration::from_secs(20);
|
|
38
|
+
|
|
39
|
+
/// `goosefs-site.properties` supplies the master address even when the URI
|
|
40
|
+
/// authority points somewhere unreachable.
|
|
41
|
+
///
|
|
42
|
+
/// Reported failure: a client configured through `$GOOSEFS_CONFIG_FILE` still
|
|
43
|
+
/// dialed the URI authority and failed with `gRPC transport error`, so a
|
|
44
|
+
/// deployment's HA master list never took effect.
|
|
45
|
+
#[tokio::test]
|
|
46
|
+
async fn site_properties_supply_the_master_address() {
|
|
47
|
+
let Ok(master) = std::env::var("OPENDAL_GOOSEFS_MASTER_ADDR") else {
|
|
48
|
+
eprintln!("skip: OPENDAL_GOOSEFS_MASTER_ADDR unset");
|
|
49
|
+
return;
|
|
50
|
+
};
|
|
51
|
+
if master.trim().is_empty() {
|
|
52
|
+
eprintln!("skip: OPENDAL_GOOSEFS_MASTER_ADDR is empty");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let root = std::env::var("OPENDAL_GOOSEFS_ROOT").unwrap_or_else(|_| "/".to_string());
|
|
56
|
+
let uri = format!("goosefs://{DUMMY_AUTHORITY}{root}");
|
|
57
|
+
|
|
58
|
+
// `GOOSEFS_MASTER_ADDR` outranks both sources under test, and a site file
|
|
59
|
+
// deployed on the host would decide the control case below.
|
|
60
|
+
unsafe {
|
|
61
|
+
std::env::remove_var("GOOSEFS_MASTER_ADDR");
|
|
62
|
+
std::env::set_var(
|
|
63
|
+
"GOOSEFS_CONFIG_FILE",
|
|
64
|
+
"/nonexistent/goosefs-site.properties",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Control: without a site file the URI authority is used, and writing
|
|
69
|
+
// through it must not reach any master.
|
|
70
|
+
let op = Operator::from_uri(uri.as_str()).expect("build operator from dummy URI");
|
|
71
|
+
let control = tokio::time::timeout(DIAL_FAILURE_TIMEOUT, op.write("resolution-control", "x"))
|
|
72
|
+
.await
|
|
73
|
+
.map_err(|_| "timed out");
|
|
74
|
+
assert!(
|
|
75
|
+
!matches!(control, Ok(Ok(_))),
|
|
76
|
+
"{DUMMY_AUTHORITY} must not serve a master, otherwise this test proves nothing"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// A site file that lists the live master must win over the URI authority.
|
|
80
|
+
let site_file = std::env::temp_dir().join(format!(
|
|
81
|
+
"opendal_goosefs_resolution_{}_site.properties",
|
|
82
|
+
std::process::id()
|
|
83
|
+
));
|
|
84
|
+
std::fs::write(
|
|
85
|
+
&site_file,
|
|
86
|
+
format!("goosefs.master.rpc.addresses={master}\n"),
|
|
87
|
+
)
|
|
88
|
+
.expect("write goosefs-site.properties");
|
|
89
|
+
unsafe { std::env::set_var("GOOSEFS_CONFIG_FILE", &site_file) };
|
|
90
|
+
|
|
91
|
+
let op =
|
|
92
|
+
Operator::from_uri(uri.as_str()).expect("build operator from dummy URI with a site file");
|
|
93
|
+
let path = format!("resolution-{}", std::process::id());
|
|
94
|
+
op.write(&path, "site-properties-win")
|
|
95
|
+
.await
|
|
96
|
+
.expect("write must reach the master listed in goosefs-site.properties");
|
|
97
|
+
let body = op.read(&path).await.expect("read back").to_bytes();
|
|
98
|
+
assert_eq!(&body[..], b"site-properties-win");
|
|
99
|
+
|
|
100
|
+
let _ = op.delete(&path).await;
|
|
101
|
+
let _ = std::fs::remove_file(&site_file);
|
|
102
|
+
unsafe { std::env::remove_var("GOOSEFS_CONFIG_FILE") };
|
|
103
|
+
}
|
|
@@ -32,10 +32,10 @@ version = { workspace = true }
|
|
|
32
32
|
all-features = true
|
|
33
33
|
|
|
34
34
|
[dependencies]
|
|
35
|
-
asyncband = { workspace = true }
|
|
35
|
+
asyncband = { workspace = true, features = ["once-cell"] }
|
|
36
36
|
futures = { workspace = true }
|
|
37
37
|
mongodb = "3.3.0"
|
|
38
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
38
|
+
opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
|
|
39
39
|
serde = { workspace = true, features = ["derive"] }
|
|
40
40
|
|
|
41
41
|
[dev-dependencies]
|
|
@@ -199,3 +199,19 @@
|
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
201
|
limitations under the License.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
------------------------------------------------------------------------------------
|
|
205
|
+
This product bundles third-party components under other open source licenses.
|
|
206
|
+
This section summarizes those components and their licenses. See the referenced
|
|
207
|
+
license files for text of these licenses.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Creative Commons Zero v1.0 Universal
|
|
211
|
+
------------------------------------
|
|
212
|
+
|
|
213
|
+
bindings/dotnet/.gitignore
|
|
214
|
+
|
|
215
|
+
The above file is derived from VisualStudio.gitignore in
|
|
216
|
+
https://github.com/github/gitignore. Its license text is in
|
|
217
|
+
bindings/dotnet/LICENSE-CC0-1.0.txt.
|
|
@@ -32,7 +32,7 @@ version = { workspace = true }
|
|
|
32
32
|
all-features = true
|
|
33
33
|
|
|
34
34
|
[dependencies]
|
|
35
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
35
|
+
opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
|
|
36
36
|
|
|
37
37
|
bytes = { workspace = true }
|
|
38
38
|
futures = { workspace = true }
|
data/core/services/hdfs/LICENSE
CHANGED
|
@@ -199,3 +199,19 @@
|
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
201
|
limitations under the License.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
------------------------------------------------------------------------------------
|
|
205
|
+
This product bundles third-party components under other open source licenses.
|
|
206
|
+
This section summarizes those components and their licenses. See the referenced
|
|
207
|
+
license files for text of these licenses.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Creative Commons Zero v1.0 Universal
|
|
211
|
+
------------------------------------
|
|
212
|
+
|
|
213
|
+
bindings/dotnet/.gitignore
|
|
214
|
+
|
|
215
|
+
The above file is derived from VisualStudio.gitignore in
|
|
216
|
+
https://github.com/github/gitignore. Its license text is in
|
|
217
|
+
bindings/dotnet/LICENSE-CC0-1.0.txt.
|
|
@@ -36,5 +36,5 @@ bytes = { workspace = true }
|
|
|
36
36
|
futures = { workspace = true }
|
|
37
37
|
hdfs-native = { version = "0.14" }
|
|
38
38
|
log = { workspace = true }
|
|
39
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
39
|
+
opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
|
|
40
40
|
serde = { workspace = true, features = ["derive"] }
|
|
@@ -199,3 +199,19 @@
|
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
201
|
limitations under the License.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
------------------------------------------------------------------------------------
|
|
205
|
+
This product bundles third-party components under other open source licenses.
|
|
206
|
+
This section summarizes those components and their licenses. See the referenced
|
|
207
|
+
license files for text of these licenses.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Creative Commons Zero v1.0 Universal
|
|
211
|
+
------------------------------------
|
|
212
|
+
|
|
213
|
+
bindings/dotnet/.gitignore
|
|
214
|
+
|
|
215
|
+
The above file is derived from VisualStudio.gitignore in
|
|
216
|
+
https://github.com/github/gitignore. Its license text is in
|
|
217
|
+
bindings/dotnet/LICENSE-CC0-1.0.txt.
|
data/core/services/hf/Cargo.toml
CHANGED
|
@@ -32,12 +32,12 @@ version = { workspace = true }
|
|
|
32
32
|
all-features = true
|
|
33
33
|
|
|
34
34
|
[dependencies]
|
|
35
|
-
asyncband = { workspace = true }
|
|
35
|
+
asyncband = { workspace = true, features = ["mutex", "once-cell"] }
|
|
36
36
|
bytes = { workspace = true }
|
|
37
37
|
hf-xet = "1.6.0"
|
|
38
38
|
http = { workspace = true }
|
|
39
39
|
log = { workspace = true }
|
|
40
|
-
opendal-core = { path = "../../core", version = "0.59.
|
|
40
|
+
opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
|
|
41
41
|
percent-encoding = "2"
|
|
42
42
|
serde = { workspace = true, features = ["derive"] }
|
|
43
43
|
serde_json = { workspace = true }
|
|
@@ -45,6 +45,6 @@ serde_json = { workspace = true }
|
|
|
45
45
|
[dev-dependencies]
|
|
46
46
|
base64 = { workspace = true }
|
|
47
47
|
futures = { workspace = true }
|
|
48
|
-
opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.59.
|
|
48
|
+
opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.59.2" }
|
|
49
49
|
serde_json = { workspace = true }
|
|
50
50
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
data/core/services/hf/LICENSE
CHANGED
|
@@ -199,3 +199,19 @@
|
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
201
|
limitations under the License.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
------------------------------------------------------------------------------------
|
|
205
|
+
This product bundles third-party components under other open source licenses.
|
|
206
|
+
This section summarizes those components and their licenses. See the referenced
|
|
207
|
+
license files for text of these licenses.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Creative Commons Zero v1.0 Universal
|
|
211
|
+
------------------------------------
|
|
212
|
+
|
|
213
|
+
bindings/dotnet/.gitignore
|
|
214
|
+
|
|
215
|
+
The above file is derived from VisualStudio.gitignore in
|
|
216
|
+
https://github.com/github/gitignore. Its license text is in
|
|
217
|
+
bindings/dotnet/LICENSE-CC0-1.0.txt.
|
|
@@ -133,6 +133,22 @@ impl HfBuilder {
|
|
|
133
133
|
self
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/// Enable caching of resolved HTTP download addresses and XET file metadata.
|
|
137
|
+
///
|
|
138
|
+
/// Defaults to `false`: every range resolves through the Hub in either
|
|
139
|
+
/// download mode. Set to `true` to share resolve results across readers on
|
|
140
|
+
/// the same backend. HTTP addresses refresh near expiry.
|
|
141
|
+
///
|
|
142
|
+
/// Enable this only when previously written files are not modified. Changed
|
|
143
|
+
/// files can remain invisible while cached results are reused, including
|
|
144
|
+
/// changes from other clients or a floating repository revision. Issued
|
|
145
|
+
/// download URLs can remain usable until expiry after Hub permissions change.
|
|
146
|
+
/// Separate authorization identities must use separately constructed backends.
|
|
147
|
+
pub fn enable_resolve_cache(mut self, enabled: bool) -> Self {
|
|
148
|
+
self.config.enable_resolve_cache = enabled;
|
|
149
|
+
self
|
|
150
|
+
}
|
|
151
|
+
|
|
136
152
|
/// Set the Hub base URL.
|
|
137
153
|
///
|
|
138
154
|
/// Configure this when your organization uses a
|
|
@@ -146,12 +162,18 @@ impl HfBuilder {
|
|
|
146
162
|
self
|
|
147
163
|
}
|
|
148
164
|
|
|
165
|
+
/// Resolve the Hub base URL: an explicit config value wins, then
|
|
166
|
+
/// `HF_ENDPOINT`, then the public Hub. A trailing slash is trimmed
|
|
167
|
+
/// because every URL is built by appending `/api/...` to this, and HF
|
|
168
|
+
/// answers the resulting `//api/...` with a 404.
|
|
149
169
|
fn hf_endpoint(&self) -> String {
|
|
150
170
|
self.config
|
|
151
171
|
.endpoint
|
|
152
172
|
.clone()
|
|
153
173
|
.or_else(|| std::env::var("HF_ENDPOINT").ok())
|
|
154
174
|
.unwrap_or_else(|| "https://huggingface.co".to_string())
|
|
175
|
+
.trim_end_matches('/')
|
|
176
|
+
.to_string()
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
/// Resolve the download mode: an explicit config value wins; otherwise a set,
|
|
@@ -260,16 +282,10 @@ impl Builder for HfBuilder {
|
|
|
260
282
|
let repo = HfRepo::new(repo_type, repo_id, Some(revision.clone()));
|
|
261
283
|
debug!("backend repo uri: {:?}", repo.uri(&root, ""));
|
|
262
284
|
|
|
285
|
+
let mut core = HfCore::build(info, capability, repo, root, token, endpoint, download_mode)?;
|
|
286
|
+
core.enable_resolve_cache = self.config.enable_resolve_cache;
|
|
263
287
|
Ok(HfBackend {
|
|
264
|
-
core: Arc::new(
|
|
265
|
-
info,
|
|
266
|
-
capability,
|
|
267
|
-
repo,
|
|
268
|
-
root,
|
|
269
|
-
token,
|
|
270
|
-
endpoint,
|
|
271
|
-
download_mode,
|
|
272
|
-
)?),
|
|
288
|
+
core: Arc::new(core),
|
|
273
289
|
})
|
|
274
290
|
}
|
|
275
291
|
}
|
|
@@ -435,6 +451,19 @@ pub(super) mod test_utils {
|
|
|
435
451
|
finish_operator(op)
|
|
436
452
|
}
|
|
437
453
|
|
|
454
|
+
/// Same public dataset as [`mbpp_operator`], but with the repo id cased
|
|
455
|
+
/// differently from the canonical `google-research-datasets/mbpp`. HF
|
|
456
|
+
/// answers every request for it with a `307` to the canonical URL.
|
|
457
|
+
pub fn miscased_mbpp_operator() -> Operator {
|
|
458
|
+
let op = Operator::new(
|
|
459
|
+
HfBuilder::default()
|
|
460
|
+
.repo_type("dataset")
|
|
461
|
+
.repo_id("Google-Research-Datasets/MBPP"),
|
|
462
|
+
)
|
|
463
|
+
.unwrap();
|
|
464
|
+
finish_operator(op)
|
|
465
|
+
}
|
|
466
|
+
|
|
438
467
|
pub fn testing_dataset_core() -> Arc<HfCore> {
|
|
439
468
|
let repo_id = std::env::var("HF_OPENDAL_DATASET").expect("HF_OPENDAL_DATASET must be set");
|
|
440
469
|
let token = std::env::var("HF_OPENDAL_TOKEN").expect("HF_OPENDAL_TOKEN must be set");
|
|
@@ -538,6 +567,18 @@ mod tests {
|
|
|
538
567
|
assert_eq!(result.as_deref(), Some("file-token"));
|
|
539
568
|
}
|
|
540
569
|
|
|
570
|
+
#[test]
|
|
571
|
+
fn hf_endpoint_trims_trailing_slash() {
|
|
572
|
+
let _guard = ENV_LOCK.lock().unwrap();
|
|
573
|
+
unsafe { std::env::remove_var("HF_ENDPOINT") };
|
|
574
|
+
assert_eq!(
|
|
575
|
+
HfBuilder::default()
|
|
576
|
+
.endpoint("https://hub.example.com/")
|
|
577
|
+
.hf_endpoint(),
|
|
578
|
+
"https://hub.example.com"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
541
582
|
#[test]
|
|
542
583
|
fn hf_endpoint_returns_default() {
|
|
543
584
|
let _guard = ENV_LOCK.lock().unwrap();
|
|
@@ -600,6 +641,33 @@ mod tests {
|
|
|
600
641
|
assert_eq!(mode, HfDownloadMode::Xet);
|
|
601
642
|
}
|
|
602
643
|
|
|
644
|
+
#[tokio::test]
|
|
645
|
+
async fn build_resolve_cache_requires_opt_in() -> Result<()> {
|
|
646
|
+
use super::super::core::test_utils::MockHttpTransport;
|
|
647
|
+
|
|
648
|
+
for enabled in [None, Some(false), Some(true)] {
|
|
649
|
+
let mut builder = HfBuilder::default()
|
|
650
|
+
.repo_type("model")
|
|
651
|
+
.repo_id("org/repo")
|
|
652
|
+
.download_mode("xet");
|
|
653
|
+
if let Some(enabled) = enabled {
|
|
654
|
+
builder = builder.enable_resolve_cache(enabled);
|
|
655
|
+
}
|
|
656
|
+
let transport = MockHttpTransport::new();
|
|
657
|
+
let ctx = OperationContext::new()
|
|
658
|
+
.with_http_transport(HttpTransporter::new(transport.clone()));
|
|
659
|
+
let op = Operator::new(builder)?.with_context(ctx);
|
|
660
|
+
for reads in 1..=2 {
|
|
661
|
+
assert_eq!(op.read("plain.txt").await?.to_vec(), b"hello");
|
|
662
|
+
assert_eq!(
|
|
663
|
+
transport.request_count(),
|
|
664
|
+
reads + usize::from(enabled == Some(true))
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
Ok(())
|
|
669
|
+
}
|
|
670
|
+
|
|
603
671
|
#[test]
|
|
604
672
|
fn build_accepts_datasets_alias() {
|
|
605
673
|
HfBuilder::default()
|
|
@@ -60,6 +60,13 @@ pub struct HfConfig {
|
|
|
60
60
|
///
|
|
61
61
|
/// See <https://huggingface.co/docs/huggingface_hub/package_reference/environment_variables#hfhubdisablexet>.
|
|
62
62
|
pub download_mode: Option<HfDownloadMode>,
|
|
63
|
+
/// Enable caching of resolved HTTP download addresses and XET file metadata.
|
|
64
|
+
///
|
|
65
|
+
/// Defaults to `false`. Set to `true` to share resolve results across readers
|
|
66
|
+
/// on the same backend. Changed files may remain invisible while cached
|
|
67
|
+
/// results are reused. See [`HfBuilder::enable_resolve_cache`] for freshness
|
|
68
|
+
/// semantics.
|
|
69
|
+
pub enable_resolve_cache: bool,
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
impl Debug for HfConfig {
|
|
@@ -73,6 +80,7 @@ impl Debug for HfConfig {
|
|
|
73
80
|
.field("revision", &self.revision)
|
|
74
81
|
.field("root", &self.root)
|
|
75
82
|
.field("download_mode", &self.download_mode)
|
|
83
|
+
.field("enable_resolve_cache", &self.enable_resolve_cache)
|
|
76
84
|
.finish_non_exhaustive()
|
|
77
85
|
}
|
|
78
86
|
}
|
|
@@ -106,6 +114,19 @@ impl opendal_core::Configurator for HfConfig {
|
|
|
106
114
|
.map(|s| HfDownloadMode::parse(s))
|
|
107
115
|
.transpose()?;
|
|
108
116
|
|
|
117
|
+
let enable_resolve_cache = opts
|
|
118
|
+
.get("enable_resolve_cache")
|
|
119
|
+
.map(|value| value.parse::<bool>())
|
|
120
|
+
.transpose()
|
|
121
|
+
.map_err(|err| {
|
|
122
|
+
opendal_core::Error::new(
|
|
123
|
+
opendal_core::ErrorKind::ConfigInvalid,
|
|
124
|
+
"enable_resolve_cache must be true or false",
|
|
125
|
+
)
|
|
126
|
+
.set_source(err)
|
|
127
|
+
})?
|
|
128
|
+
.unwrap_or_default();
|
|
129
|
+
|
|
109
130
|
if !path.is_empty() {
|
|
110
131
|
// Full URI like "hf://datasets/user/repo@rev/path"
|
|
111
132
|
let parsed = HfUri::parse(&path)?;
|
|
@@ -117,6 +138,7 @@ impl opendal_core::Configurator for HfConfig {
|
|
|
117
138
|
token: opts.get("token").cloned(),
|
|
118
139
|
endpoint: opts.get("endpoint").cloned(),
|
|
119
140
|
download_mode,
|
|
141
|
+
enable_resolve_cache,
|
|
120
142
|
})
|
|
121
143
|
} else {
|
|
122
144
|
// Bare scheme from via_iter, all config is in options.
|
|
@@ -138,6 +160,7 @@ impl opendal_core::Configurator for HfConfig {
|
|
|
138
160
|
token: opts.get("token").cloned(),
|
|
139
161
|
endpoint: opts.get("endpoint").cloned(),
|
|
140
162
|
download_mode,
|
|
163
|
+
enable_resolve_cache,
|
|
141
164
|
})
|
|
142
165
|
}
|
|
143
166
|
}
|
|
@@ -222,4 +245,39 @@ mod tests {
|
|
|
222
245
|
let cfg = HfConfig::from_uri(&uri).unwrap();
|
|
223
246
|
assert_eq!(cfg.download_mode.unwrap_or_default(), HfDownloadMode::Xet);
|
|
224
247
|
}
|
|
248
|
+
|
|
249
|
+
#[test]
|
|
250
|
+
fn from_uri_resolve_cache_options() {
|
|
251
|
+
for scheme in ["hf://datasets/user/repo", "huggingface"] {
|
|
252
|
+
for (value, expected) in [(None, false), (Some("false"), false), (Some("true"), true)] {
|
|
253
|
+
let mut options = vec![("repo_type", "dataset"), ("repo_id", "user/repo")];
|
|
254
|
+
if let Some(value) = value {
|
|
255
|
+
options.push(("enable_resolve_cache", value));
|
|
256
|
+
}
|
|
257
|
+
let uri = OperatorUri::new(
|
|
258
|
+
scheme,
|
|
259
|
+
options
|
|
260
|
+
.into_iter()
|
|
261
|
+
.map(|(key, value)| (key.to_string(), value.to_string())),
|
|
262
|
+
)
|
|
263
|
+
.unwrap();
|
|
264
|
+
assert_eq!(
|
|
265
|
+
HfConfig::from_uri(&uri).unwrap().enable_resolve_cache,
|
|
266
|
+
expected
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let uri = OperatorUri::new(
|
|
271
|
+
scheme,
|
|
272
|
+
[("enable_resolve_cache".to_string(), "invalid".to_string())],
|
|
273
|
+
)
|
|
274
|
+
.unwrap();
|
|
275
|
+
let err = HfConfig::from_uri(&uri).unwrap_err();
|
|
276
|
+
assert_eq!(err.kind(), opendal_core::ErrorKind::ConfigInvalid);
|
|
277
|
+
assert!(
|
|
278
|
+
err.to_string()
|
|
279
|
+
.contains("enable_resolve_cache must be true or false")
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
225
283
|
}
|