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
|
@@ -64,13 +64,11 @@ impl GoosefsBuilder {
|
|
|
64
64
|
/// Single master: `"10.0.0.1:9200"`
|
|
65
65
|
/// HA (comma-separated): `"10.0.0.1:9200,10.0.0.2:9200,10.0.0.3:9200"`
|
|
66
66
|
///
|
|
67
|
-
///
|
|
68
|
-
/// `goosefs-site.properties`
|
|
69
|
-
///
|
|
70
|
-
///
|
|
71
|
-
///
|
|
72
|
-
/// §1. `build()` fails with `ConfigInvalid` only if **no** source
|
|
73
|
-
/// supplies a master address.
|
|
67
|
+
/// This is the lowest-priority source: `build()` uses it only when
|
|
68
|
+
/// neither `GOOSEFS_MASTER_ADDR` nor `goosefs-site.properties` declares a
|
|
69
|
+
/// master address, and fails with `ConfigInvalid` when no source supplies
|
|
70
|
+
/// one. See [`crate::GoosefsConfig::master_addr`] for the full resolution
|
|
71
|
+
/// order and its rationale.
|
|
74
72
|
pub fn master_addr(mut self, addr: &str) -> Self {
|
|
75
73
|
if !addr.is_empty() {
|
|
76
74
|
self.config.master_addr = Some(addr.to_string());
|
|
@@ -124,6 +122,160 @@ impl GoosefsBuilder {
|
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
|
|
125
|
+
/// The source that supplied the master addresses of an auto-loaded SDK config.
|
|
126
|
+
///
|
|
127
|
+
/// `goosefs_sdk::config::GoosefsConfig::from_properties_auto()` always returns
|
|
128
|
+
/// a master address: when neither `GOOSEFS_MASTER_ADDR` nor
|
|
129
|
+
/// `goosefs-site.properties` declares one, it keeps the SDK default
|
|
130
|
+
/// `127.0.0.1:9200`. The loaded value therefore cannot tell "a real source
|
|
131
|
+
/// configured this" from "nothing configured this", and the builder has to
|
|
132
|
+
/// ask which source actually spoke.
|
|
133
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
134
|
+
enum MasterAddrSource {
|
|
135
|
+
/// `GOOSEFS_MASTER_ADDR` supplied the addresses.
|
|
136
|
+
Env,
|
|
137
|
+
/// `goosefs-site.properties` declared `goosefs.master.rpc.addresses` or
|
|
138
|
+
/// `goosefs.master.hostname`.
|
|
139
|
+
SiteProperties,
|
|
140
|
+
/// Neither spoke; the loaded addresses are the SDK defaults.
|
|
141
|
+
Unset,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Apply the master-address resolution order to an auto-loaded SDK config.
|
|
145
|
+
///
|
|
146
|
+
/// `goosefs-site.properties` carries a deployment's whole HA master list,
|
|
147
|
+
/// which a URI authority such as `goosefs://host:9200/path` cannot express, so
|
|
148
|
+
/// a site file that declares masters outranks `explicit` (the `master_addr`
|
|
149
|
+
/// config key, whether it arrived from the builder, the option map, or the URI
|
|
150
|
+
/// authority). `GOOSEFS_MASTER_ADDR` stays on top as the per-process override:
|
|
151
|
+
///
|
|
152
|
+
/// ```text
|
|
153
|
+
/// GOOSEFS_MASTER_ADDR > goosefs-site.properties > master_addr
|
|
154
|
+
/// ```
|
|
155
|
+
///
|
|
156
|
+
/// `from_properties_auto()` already resolved the first two sources, so
|
|
157
|
+
/// `explicit` is written into `config` only when neither spoke. Returns
|
|
158
|
+
/// `ConfigInvalid` when `explicit` holds no address after trimming, and when
|
|
159
|
+
/// no source at all supplies one.
|
|
160
|
+
fn apply_master_addr_precedence(
|
|
161
|
+
config: &mut goosefs_sdk::config::GoosefsConfig,
|
|
162
|
+
explicit: Option<&str>,
|
|
163
|
+
) -> Result<()> {
|
|
164
|
+
let explicit_addrs = match explicit {
|
|
165
|
+
Some(master_addr) => {
|
|
166
|
+
let addrs: Vec<String> = master_addr
|
|
167
|
+
.split(',')
|
|
168
|
+
.map(|s| s.trim().to_string())
|
|
169
|
+
.filter(|s| !s.is_empty())
|
|
170
|
+
.collect();
|
|
171
|
+
if addrs.is_empty() {
|
|
172
|
+
return Err(Error::new(
|
|
173
|
+
ErrorKind::ConfigInvalid,
|
|
174
|
+
"master_addr is empty after trimming",
|
|
175
|
+
)
|
|
176
|
+
.with_operation("Builder::build")
|
|
177
|
+
.with_context("service", GOOSEFS_SCHEME));
|
|
178
|
+
}
|
|
179
|
+
Some(addrs)
|
|
180
|
+
}
|
|
181
|
+
None => None,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
match detect_master_addr_source() {
|
|
185
|
+
MasterAddrSource::Unset => {
|
|
186
|
+
let Some(addrs) = explicit_addrs else {
|
|
187
|
+
return Err(Error::new(
|
|
188
|
+
ErrorKind::ConfigInvalid,
|
|
189
|
+
"master_addr is not configured: set it via GoosefsBuilder::master_addr(...), \
|
|
190
|
+
the `master_addr` config key, the GOOSEFS_MASTER_ADDR env var, \
|
|
191
|
+
or `goosefs.master.hostname`/`goosefs.master.rpc.addresses` in goosefs-site.properties",
|
|
192
|
+
)
|
|
193
|
+
.with_operation("Builder::build")
|
|
194
|
+
.with_context("service", GOOSEFS_SCHEME));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
config.master_addr = addrs[0].clone();
|
|
198
|
+
config.master_addrs = if addrs.len() > 1 { addrs } else { Vec::new() };
|
|
199
|
+
}
|
|
200
|
+
source => {
|
|
201
|
+
if let Some(addrs) = explicit_addrs {
|
|
202
|
+
debug!(
|
|
203
|
+
"GoosefsBuilder ignores master_addr {addrs:?}: {source:?} supplies {} (addrs={:?})",
|
|
204
|
+
config.master_addr, config.master_addrs
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
Ok(())
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Report which source supplied the master addresses that
|
|
214
|
+
/// `from_properties_auto()` loaded.
|
|
215
|
+
fn detect_master_addr_source() -> MasterAddrSource {
|
|
216
|
+
// Probe `GOOSEFS_MASTER_ADDR` through the SDK's own parser so that the
|
|
217
|
+
// `gfs://h1:9200,h2:9200/root` URI form and the bare comma list are
|
|
218
|
+
// recognised exactly as `from_properties_auto()` recognises them. Blanking
|
|
219
|
+
// the address first makes an unset, empty, or malformed value observable:
|
|
220
|
+
// in those cases `apply_env()` leaves the field untouched.
|
|
221
|
+
let probe = goosefs_sdk::config::GoosefsConfig {
|
|
222
|
+
master_addr: String::new(),
|
|
223
|
+
master_addrs: Vec::new(),
|
|
224
|
+
..Default::default()
|
|
225
|
+
}
|
|
226
|
+
.apply_env();
|
|
227
|
+
if !probe.master_addr.is_empty() || !probe.master_addrs.is_empty() {
|
|
228
|
+
return MasterAddrSource::Env;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// The SDK searches `$GOOSEFS_CONFIG_FILE`, `$GOOSEFS_CONF_DIR`,
|
|
232
|
+
// `$GOOSEFS_HOME/conf`, `~/.goosefs`, and `/etc/goosefs` for
|
|
233
|
+
// `goosefs-site.properties`. An unreadable file is left to
|
|
234
|
+
// `from_properties_auto()`, which already failed the build above.
|
|
235
|
+
if let Some(path) = goosefs_sdk::config::discover_config_file()
|
|
236
|
+
&& let Ok(content) = std::fs::read_to_string(&path)
|
|
237
|
+
&& site_properties_declare_master(&content)
|
|
238
|
+
{
|
|
239
|
+
return MasterAddrSource::SiteProperties;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
MasterAddrSource::Unset
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Report whether `goosefs-site.properties` declares a master address.
|
|
246
|
+
///
|
|
247
|
+
/// The SDK's properties parser is private, so key lookup repeats its
|
|
248
|
+
/// documented Java `Properties.load()` rules here: `#` and `!` start a comment
|
|
249
|
+
/// line, the first `=` (else the first `:`) separates key from value, and the
|
|
250
|
+
/// last assignment of a key wins. `goosefs.master.rpc.addresses` shadows
|
|
251
|
+
/// `goosefs.master.hostname` whenever it is present, matching the order in
|
|
252
|
+
/// which the SDK consults the two keys.
|
|
253
|
+
fn site_properties_declare_master(content: &str) -> bool {
|
|
254
|
+
let mut addresses = None;
|
|
255
|
+
let mut hostname = None;
|
|
256
|
+
|
|
257
|
+
for line in content.lines() {
|
|
258
|
+
let line = line.trim();
|
|
259
|
+
if line.is_empty() || line.starts_with('#') || line.starts_with('!') {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
let Some(sep) = line.find('=').or_else(|| line.find(':')) else {
|
|
263
|
+
continue;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
match line[..sep].trim() {
|
|
267
|
+
"goosefs.master.rpc.addresses" => addresses = Some(line[sep + 1..].trim()),
|
|
268
|
+
"goosefs.master.hostname" => hostname = Some(line[sep + 1..].trim()),
|
|
269
|
+
_ => {}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
match addresses {
|
|
274
|
+
Some(addresses) => addresses.split(',').any(|addr| !addr.trim().is_empty()),
|
|
275
|
+
None => hostname.is_some_and(|hostname| !hostname.is_empty()),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
127
279
|
impl Builder for GoosefsBuilder {
|
|
128
280
|
type Config = GoosefsConfig;
|
|
129
281
|
|
|
@@ -156,8 +308,9 @@ impl Builder for GoosefsBuilder {
|
|
|
156
308
|
// * properties file present but malformed → hard-fail
|
|
157
309
|
// (broken config must not be silently dropped)
|
|
158
310
|
//
|
|
159
|
-
// Builder-explicit fields
|
|
160
|
-
//
|
|
311
|
+
// Builder-explicit fields overlay this config afterwards. The master
|
|
312
|
+
// address is the one exception: Step 2 keeps the properties/env value
|
|
313
|
+
// on top, see `apply_master_addr_precedence`.
|
|
161
314
|
let mut goosefs_config = goosefs_sdk::config::GoosefsConfig::from_properties_auto()
|
|
162
315
|
.map_err(|e| {
|
|
163
316
|
Error::new(
|
|
@@ -171,50 +324,15 @@ impl Builder for GoosefsBuilder {
|
|
|
171
324
|
// Root always comes from OpenDAL (it's an OpenDAL-layer concept).
|
|
172
325
|
goosefs_config.root = root.clone();
|
|
173
326
|
|
|
174
|
-
// ── Step 2:
|
|
175
|
-
|
|
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
|
-
// After properties/env auto-load + builder overlay, we must still
|
|
201
|
-
// have at least one usable master address. If we don't, fail fast
|
|
202
|
-
// with a message that points the user at all three sources.
|
|
203
|
-
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
|
-
}
|
|
327
|
+
// ── Step 2: resolve the master address ────────────────────────────────
|
|
328
|
+
apply_master_addr_precedence(&mut goosefs_config, self.config.master_addr.as_deref())?;
|
|
213
329
|
debug!(
|
|
214
330
|
"GoosefsBuilder use master_addr {} (addrs={:?})",
|
|
215
331
|
goosefs_config.master_addr, goosefs_config.master_addrs
|
|
216
332
|
);
|
|
217
333
|
|
|
334
|
+
// ── Step 3: overlay the remaining builder-explicit fields ─────────────
|
|
335
|
+
|
|
218
336
|
if let Some(block_size) = self.config.block_size {
|
|
219
337
|
goosefs_config.block_size = block_size;
|
|
220
338
|
}
|
|
@@ -258,7 +376,7 @@ impl Builder for GoosefsBuilder {
|
|
|
258
376
|
goosefs_config = goosefs_config.with_auth_username(auth_username);
|
|
259
377
|
}
|
|
260
378
|
|
|
261
|
-
// ── Step
|
|
379
|
+
// ── Step 4: validate the final merged config ──────────────────────────
|
|
262
380
|
goosefs_config.validate().map_err(|e| {
|
|
263
381
|
Error::new(
|
|
264
382
|
ErrorKind::ConfigInvalid,
|
|
@@ -411,9 +529,215 @@ impl Service for GoosefsBackend {
|
|
|
411
529
|
#[cfg(test)]
|
|
412
530
|
mod tests {
|
|
413
531
|
use super::*;
|
|
532
|
+
use goosefs_sdk::config::ENV_CONF_DIR;
|
|
533
|
+
use goosefs_sdk::config::ENV_CONFIG_FILE;
|
|
534
|
+
use goosefs_sdk::config::ENV_HOME;
|
|
535
|
+
use goosefs_sdk::config::ENV_MASTER_ADDR;
|
|
536
|
+
use std::path::PathBuf;
|
|
537
|
+
use std::sync::Mutex;
|
|
538
|
+
|
|
539
|
+
/// `GOOSEFS_*` vars are process-global; serialize every test that
|
|
540
|
+
/// exercises master-address resolution through them.
|
|
541
|
+
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
|
542
|
+
|
|
543
|
+
const SITE_HA: &str =
|
|
544
|
+
"goosefs.master.rpc.addresses=172.31.5.10:9200,172.31.5.2:9200,172.31.5.11:9200\n";
|
|
545
|
+
const SITE_NO_MASTER: &str = "goosefs.user.block.size.bytes.default=4MB\n";
|
|
546
|
+
|
|
547
|
+
/// Write a `goosefs-site.properties` fixture, point `$GOOSEFS_CONFIG_FILE`
|
|
548
|
+
/// at it, and clear the other resolution inputs.
|
|
549
|
+
///
|
|
550
|
+
/// A file is written even for the "no master keys" cases so that discovery
|
|
551
|
+
/// stops at `$GOOSEFS_CONFIG_FILE` instead of reaching a `~/.goosefs` or
|
|
552
|
+
/// `/etc/goosefs` file that happens to exist on the host.
|
|
553
|
+
fn site_properties(name: &str, content: &str) -> PathBuf {
|
|
554
|
+
let path = std::env::temp_dir().join(format!(
|
|
555
|
+
"opendal_goosefs_{}_{name}_site.properties",
|
|
556
|
+
std::process::id()
|
|
557
|
+
));
|
|
558
|
+
std::fs::write(&path, content).expect("write goosefs-site.properties");
|
|
559
|
+
|
|
560
|
+
unsafe {
|
|
561
|
+
std::env::set_var(ENV_CONFIG_FILE, &path);
|
|
562
|
+
std::env::remove_var(ENV_CONF_DIR);
|
|
563
|
+
std::env::remove_var(ENV_HOME);
|
|
564
|
+
std::env::remove_var(ENV_MASTER_ADDR);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
path
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
fn clear_site_properties(path: &PathBuf) {
|
|
571
|
+
unsafe {
|
|
572
|
+
std::env::remove_var(ENV_CONFIG_FILE);
|
|
573
|
+
std::env::remove_var(ENV_MASTER_ADDR);
|
|
574
|
+
}
|
|
575
|
+
let _ = std::fs::remove_file(path);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/// Resolve master addresses exactly as `build()` does: auto-load, then
|
|
579
|
+
/// apply the precedence.
|
|
580
|
+
fn resolve(explicit: Option<&str>) -> Result<(String, Vec<String>)> {
|
|
581
|
+
let mut config = goosefs_sdk::config::GoosefsConfig::from_properties_auto()
|
|
582
|
+
.expect("auto-load must succeed");
|
|
583
|
+
apply_master_addr_precedence(&mut config, explicit)?;
|
|
584
|
+
Ok((config.master_addr, config.master_addrs))
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/// A site file that declares `goosefs.master.rpc.addresses` supplies the
|
|
588
|
+
/// HA list even when the URI authority carries an unreachable address.
|
|
589
|
+
///
|
|
590
|
+
/// This is the reported failure: with `GOOSEFS_CONFIG_FILE` pointing at a
|
|
591
|
+
/// file that lists the real masters, the client still dialed the dummy
|
|
592
|
+
/// address from `goosefs://192.0.2.5:9999/...`.
|
|
593
|
+
#[test]
|
|
594
|
+
fn site_properties_outrank_uri_authority() {
|
|
595
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
596
|
+
let path = site_properties("site_over_uri", SITE_HA);
|
|
597
|
+
|
|
598
|
+
let (master_addr, master_addrs) =
|
|
599
|
+
resolve(Some("192.0.2.5:9999")).expect("resolution must succeed");
|
|
600
|
+
|
|
601
|
+
assert_eq!(master_addr, "172.31.5.10:9200");
|
|
602
|
+
assert_eq!(
|
|
603
|
+
master_addrs,
|
|
604
|
+
vec![
|
|
605
|
+
"172.31.5.10:9200".to_string(),
|
|
606
|
+
"172.31.5.2:9200".to_string(),
|
|
607
|
+
"172.31.5.11:9200".to_string(),
|
|
608
|
+
]
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
clear_site_properties(&path);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/// Without a URI authority or `GOOSEFS_MASTER_ADDR`, a declared site file
|
|
615
|
+
/// still supplies the masters — `goosefs:///path` must not be rejected for
|
|
616
|
+
/// a missing host.
|
|
617
|
+
#[test]
|
|
618
|
+
fn site_properties_resolve_without_explicit_addr() {
|
|
619
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
620
|
+
let path = site_properties("site_only", SITE_HA);
|
|
621
|
+
|
|
622
|
+
let (master_addr, master_addrs) = resolve(None).expect("resolution must succeed");
|
|
623
|
+
|
|
624
|
+
assert_eq!(master_addr, "172.31.5.10:9200");
|
|
625
|
+
assert_eq!(master_addrs.len(), 3);
|
|
626
|
+
|
|
627
|
+
clear_site_properties(&path);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/// `GOOSEFS_MASTER_ADDR` is the per-process override and outranks both the
|
|
631
|
+
/// site file and the URI authority.
|
|
632
|
+
#[test]
|
|
633
|
+
fn env_master_addr_outranks_site_properties_and_uri_authority() {
|
|
634
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
635
|
+
let path = site_properties("env_over_site", SITE_HA);
|
|
636
|
+
unsafe { std::env::set_var(ENV_MASTER_ADDR, "10.0.0.7:9200") };
|
|
637
|
+
|
|
638
|
+
let (master_addr, master_addrs) =
|
|
639
|
+
resolve(Some("192.0.2.5:9999")).expect("resolution must succeed");
|
|
640
|
+
|
|
641
|
+
assert_eq!(master_addr, "10.0.0.7:9200");
|
|
642
|
+
assert!(
|
|
643
|
+
master_addrs.is_empty(),
|
|
644
|
+
"a single env address must not populate the HA list, got {master_addrs:?}"
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
clear_site_properties(&path);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/// A site file without master keys leaves `master_addr` in charge, so a
|
|
651
|
+
/// URI authority or option-map address is used as before.
|
|
652
|
+
#[test]
|
|
653
|
+
fn explicit_addr_applies_when_no_source_declares_masters() {
|
|
654
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
655
|
+
let path = site_properties("explicit_only", SITE_NO_MASTER);
|
|
656
|
+
|
|
657
|
+
let (master_addr, master_addrs) =
|
|
658
|
+
resolve(Some("10.0.0.1:9200,10.0.0.2:9200")).expect("resolution must succeed");
|
|
659
|
+
|
|
660
|
+
assert_eq!(master_addr, "10.0.0.1:9200");
|
|
661
|
+
assert_eq!(
|
|
662
|
+
master_addrs,
|
|
663
|
+
vec!["10.0.0.1:9200".to_string(), "10.0.0.2:9200".to_string()]
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
clear_site_properties(&path);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/// When no source declares a master address, `build()` must fail instead
|
|
670
|
+
/// of silently dialing the SDK default `127.0.0.1:9200`.
|
|
671
|
+
#[test]
|
|
672
|
+
fn missing_master_addr_fails() {
|
|
673
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
674
|
+
let path = site_properties("missing_master", SITE_NO_MASTER);
|
|
675
|
+
|
|
676
|
+
let err = resolve(None).expect_err("resolution must fail without any master address");
|
|
677
|
+
assert_eq!(err.kind(), ErrorKind::ConfigInvalid);
|
|
678
|
+
assert!(
|
|
679
|
+
err.to_string().contains("master_addr is not configured"),
|
|
680
|
+
"unexpected error message: {err}"
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
clear_site_properties(&path);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/// `GOOSEFS_MASTER_ADDR` also accepts the SDK's `gfs://` URI form, and
|
|
687
|
+
/// resolution must recognise it as a real source rather than falling
|
|
688
|
+
/// through to `master_addr`.
|
|
689
|
+
#[test]
|
|
690
|
+
fn env_master_addr_accepts_gfs_uri_form() {
|
|
691
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
692
|
+
let path = site_properties("env_gfs_uri", SITE_NO_MASTER);
|
|
693
|
+
unsafe { std::env::set_var(ENV_MASTER_ADDR, "gfs://10.0.0.1:9200,10.0.0.2:9200/lance") };
|
|
694
|
+
|
|
695
|
+
let (master_addr, master_addrs) =
|
|
696
|
+
resolve(Some("192.0.2.5:9999")).expect("resolution must succeed");
|
|
697
|
+
|
|
698
|
+
assert_eq!(master_addr, "10.0.0.1:9200");
|
|
699
|
+
assert_eq!(master_addrs.len(), 2);
|
|
700
|
+
|
|
701
|
+
clear_site_properties(&path);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
#[test]
|
|
705
|
+
fn site_properties_declare_master_reads_both_keys() {
|
|
706
|
+
assert!(site_properties_declare_master(
|
|
707
|
+
"goosefs.master.rpc.addresses=10.0.0.1:9200,10.0.0.2:9200\n"
|
|
708
|
+
));
|
|
709
|
+
assert!(site_properties_declare_master(
|
|
710
|
+
"goosefs.master.hostname = master-1\ngoosefs.master.rpc.port=9200\n"
|
|
711
|
+
));
|
|
712
|
+
// Java `Properties.load()` also accepts `:` as the separator.
|
|
713
|
+
assert!(site_properties_declare_master(
|
|
714
|
+
"goosefs.master.hostname:master-1\n"
|
|
715
|
+
));
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#[test]
|
|
719
|
+
fn site_properties_declare_master_rejects_absent_and_blank_keys() {
|
|
720
|
+
assert!(!site_properties_declare_master(SITE_NO_MASTER));
|
|
721
|
+
assert!(!site_properties_declare_master(
|
|
722
|
+
"# goosefs.master.rpc.addresses=10.0.0.1:9200\n! goosefs.master.hostname=master-1\n"
|
|
723
|
+
));
|
|
724
|
+
assert!(!site_properties_declare_master(
|
|
725
|
+
"goosefs.master.hostname=\n"
|
|
726
|
+
));
|
|
727
|
+
assert!(!site_properties_declare_master(
|
|
728
|
+
"goosefs.master.rpc.addresses= , ,\n"
|
|
729
|
+
));
|
|
730
|
+
// The SDK consults `addresses` first and never falls back to
|
|
731
|
+
// `hostname` once the key is present, so a blank list declares
|
|
732
|
+
// nothing here either.
|
|
733
|
+
assert!(!site_properties_declare_master(
|
|
734
|
+
"goosefs.master.rpc.addresses=\ngoosefs.master.hostname=master-1\n"
|
|
735
|
+
));
|
|
736
|
+
}
|
|
414
737
|
|
|
415
738
|
#[test]
|
|
416
739
|
fn test_builder_build() {
|
|
740
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
417
741
|
let builder = GoosefsBuilder::default()
|
|
418
742
|
.root("/data")
|
|
419
743
|
.master_addr("127.0.0.1:9200")
|
|
@@ -423,6 +747,7 @@ mod tests {
|
|
|
423
747
|
|
|
424
748
|
#[test]
|
|
425
749
|
fn test_builder_ha() {
|
|
750
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
426
751
|
let builder = GoosefsBuilder::default()
|
|
427
752
|
.root("/data")
|
|
428
753
|
.master_addr("10.0.0.1:9200,10.0.0.2:9200,10.0.0.3:9200")
|
|
@@ -437,6 +762,7 @@ mod tests {
|
|
|
437
762
|
/// before any auto-load value can rescue it.
|
|
438
763
|
#[test]
|
|
439
764
|
fn test_builder_blank_master_addr_fails() {
|
|
765
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
440
766
|
let err = GoosefsBuilder::default()
|
|
441
767
|
.root("/data")
|
|
442
768
|
.master_addr(" , , ")
|
|
@@ -451,6 +777,7 @@ mod tests {
|
|
|
451
777
|
|
|
452
778
|
#[test]
|
|
453
779
|
fn test_capability_rename_with_if_not_exists() {
|
|
780
|
+
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
|
454
781
|
let backend = GoosefsBuilder::default()
|
|
455
782
|
.root("/data")
|
|
456
783
|
.master_addr("127.0.0.1:9200")
|
|
@@ -46,12 +46,18 @@ pub struct GoosefsConfig {
|
|
|
46
46
|
///
|
|
47
47
|
/// Resolution precedence at `build()` time (highest → lowest), following
|
|
48
48
|
/// [goosefs-sdk `docs/CLIENT_CONFIGURATION.md`](https://github.com/Tencent/tencent-goosefs-rust-sdk/blob/main/docs/CLIENT_CONFIGURATION.md) §1:
|
|
49
|
-
/// 1.
|
|
50
|
-
/// 2. `
|
|
51
|
-
/// 3. `goosefs.master.rpc.addresses` / `goosefs.master.hostname` in
|
|
49
|
+
/// 1. `GOOSEFS_MASTER_ADDR` environment variable
|
|
50
|
+
/// 2. `goosefs.master.rpc.addresses` / `goosefs.master.hostname` in
|
|
52
51
|
/// `goosefs-site.properties`
|
|
52
|
+
/// 3. This field (from the builder, the OpenDAL config map, or the URI
|
|
53
|
+
/// authority of `goosefs://host:port/path`)
|
|
53
54
|
///
|
|
54
|
-
///
|
|
55
|
+
/// A site file that declares masters therefore outranks this field: the
|
|
56
|
+
/// file carries the deployment's whole HA master list, which a single URI
|
|
57
|
+
/// authority cannot express. Set `GOOSEFS_MASTER_ADDR` to override a
|
|
58
|
+
/// deployed site file for one process.
|
|
59
|
+
///
|
|
60
|
+
/// `build()` fails with `ConfigInvalid` when **none** of the above
|
|
55
61
|
/// supplies a master address.
|
|
56
62
|
pub master_addr: Option<String>,
|
|
57
63
|
|
|
@@ -164,12 +170,14 @@ mod tests {
|
|
|
164
170
|
|
|
165
171
|
/// HA note: since URIs don't allow commas in `host`, HA mode is
|
|
166
172
|
/// expressed through the extra-option/env-var pathway, not through the
|
|
167
|
-
/// URI authority. When both are present the URI authority **wins**
|
|
168
|
-
///
|
|
169
|
-
///
|
|
170
|
-
///
|
|
171
|
-
///
|
|
172
|
-
///
|
|
173
|
+
/// URI authority. When both are present the URI authority **wins** the
|
|
174
|
+
/// `master_addr` slot — a user who typed `goosefs://host:port/` must not
|
|
175
|
+
/// be silently redirected by a stale option map. The full HA list
|
|
176
|
+
/// therefore reaches `build()` only when the caller goes through
|
|
177
|
+
/// `from_iter` / `Operator::via_iter` without the URI authority, which is
|
|
178
|
+
/// covered by [`from_iter_picks_up_every_known_field`], or through
|
|
179
|
+
/// `goosefs-site.properties` / `GOOSEFS_MASTER_ADDR`, which outrank
|
|
180
|
+
/// `master_addr` inside `build()`.
|
|
173
181
|
#[test]
|
|
174
182
|
fn from_uri_authority_overrides_extra_master_addr() {
|
|
175
183
|
let uri = OperatorUri::new(
|
|
@@ -19,12 +19,12 @@ use std::fmt::Debug;
|
|
|
19
19
|
use std::sync::Arc;
|
|
20
20
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
21
21
|
|
|
22
|
+
use asyncband::rwlock::RwLock;
|
|
22
23
|
use goosefs_sdk::client::MasterClient;
|
|
23
24
|
use goosefs_sdk::config::GoosefsConfig as ClientConfig;
|
|
24
25
|
use goosefs_sdk::context::FileSystemContext;
|
|
25
26
|
use goosefs_sdk::io::{GoosefsFileReader, GoosefsFileWriter};
|
|
26
27
|
use goosefs_sdk::proto::grpc::file::FileInfo;
|
|
27
|
-
use tokio::sync::RwLock;
|
|
28
28
|
|
|
29
29
|
use opendal_core::raw::*;
|
|
30
30
|
use opendal_core::*;
|
|
@@ -23,6 +23,8 @@ which means it connects directly to GooseFS Master (port 9200) and
|
|
|
23
23
|
Worker (port 9203) without requiring a Proxy component.
|
|
24
24
|
|
|
25
25
|
Features:
|
|
26
|
+
- **Read metadata**: Streams expose full-file metadata from the SDK reader before
|
|
27
|
+
consuming the body. Bounded reads do not issue a separate stat to obtain it.
|
|
26
28
|
- **HA support**: Comma-separated master addresses for automatic Primary Master discovery.
|
|
27
29
|
- **Block-level I/O**: Data reads/writes go through block-level gRPC bidirectional streaming.
|
|
28
30
|
- **Consistent hash routing**: Worker selection uses consistent hashing on block IDs.
|
|
@@ -32,6 +34,9 @@ Features:
|
|
|
32
34
|
- **Rename parent directories**: `rename` creates a missing destination parent only
|
|
33
35
|
when Master reports it missing. Write-via-temp publish does not call
|
|
34
36
|
`CreateDirectory` for a parent that `CreateFile(recursive)` already created.
|
|
37
|
+
- **Optional client caches**: `goosefs-sdk` 0.2.1 keeps metadata and page caches
|
|
38
|
+
behind Cargo features so the default build stays a gRPC client. Enable them
|
|
39
|
+
on this crate; see [Optional client caches](#optional-client-caches).
|
|
35
40
|
|
|
36
41
|
## Configuration
|
|
37
42
|
|
|
@@ -39,6 +44,71 @@ Use [`crate::GoosefsConfig`] for serializable configuration and this builder's
|
|
|
39
44
|
methods for direct construction. The field and method documentation defines
|
|
40
45
|
accepted values, defaults, and environment interaction.
|
|
41
46
|
|
|
47
|
+
### Master address resolution
|
|
48
|
+
|
|
49
|
+
`build()` resolves the master addresses from three sources, highest priority
|
|
50
|
+
first:
|
|
51
|
+
|
|
52
|
+
1. the `GOOSEFS_MASTER_ADDR` environment variable, either a comma-separated
|
|
53
|
+
list or the SDK's `gfs://h1:9200,h2:9200/root` URI form;
|
|
54
|
+
2. `goosefs.master.rpc.addresses` or `goosefs.master.hostname` in
|
|
55
|
+
`goosefs-site.properties`, discovered through `$GOOSEFS_CONFIG_FILE`,
|
|
56
|
+
`$GOOSEFS_CONF_DIR`, `$GOOSEFS_HOME/conf`, `~/.goosefs`, and `/etc/goosefs`.
|
|
57
|
+
Use `$GOOSEFS_CONFIG_FILE` to point at a file outside those directories;
|
|
58
|
+
3. the `master_addr` config key, which also receives the URI authority of
|
|
59
|
+
`goosefs://host:port/path`.
|
|
60
|
+
|
|
61
|
+
A site file that declares masters outranks `master_addr` because the file
|
|
62
|
+
carries a deployment's whole HA master list, which a single URI authority
|
|
63
|
+
cannot express. Set `GOOSEFS_MASTER_ADDR` to override a deployed site file for
|
|
64
|
+
one process. `build()` fails with `ConfigInvalid` when no source supplies an
|
|
65
|
+
address; it never falls back to `127.0.0.1:9200`.
|
|
66
|
+
|
|
67
|
+
| `goosefs-site.properties` | `GOOSEFS_MASTER_ADDR` | `master_addr` / URI authority | Master addresses used |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| declares masters | set | any | `GOOSEFS_MASTER_ADDR` |
|
|
70
|
+
| declares masters | unset | any or absent | site file |
|
|
71
|
+
| absent, or no master keys | set | any | `GOOSEFS_MASTER_ADDR` |
|
|
72
|
+
| absent, or no master keys | unset | set | `master_addr` |
|
|
73
|
+
| absent, or no master keys | unset | absent | none — `ConfigInvalid` |
|
|
74
|
+
|
|
75
|
+
### Optional client caches
|
|
76
|
+
|
|
77
|
+
`goosefs-sdk` 0.2.1 compiles the client metadata cache and the disk-backed page
|
|
78
|
+
cache only when their Cargo features are enabled. The default
|
|
79
|
+
`services-goosefs` / `opendal-service-goosefs` build stays a gRPC client.
|
|
80
|
+
|
|
81
|
+
Enable a cache on this crate. Applications that use the `opendal` facade still
|
|
82
|
+
add `opendal-service-goosefs` as a direct dependency so Cargo unifies the
|
|
83
|
+
features into the same build:
|
|
84
|
+
|
|
85
|
+
| Feature | Compiles |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `metadata-cache` | Process-local status / listing LRU |
|
|
88
|
+
| `page-cache` | Disk-backed page cache via `tokio::fs` |
|
|
89
|
+
| `page-cache-io-uring` | Page cache plus the Linux io_uring backend |
|
|
90
|
+
|
|
91
|
+
```shell
|
|
92
|
+
cargo add opendal --features services-goosefs
|
|
93
|
+
cargo add opendal-service-goosefs --features metadata-cache,page-cache-io-uring
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`page-cache-io-uring` already enables `page-cache`. On non-Linux targets the
|
|
97
|
+
SDK still builds the portable `tokio::fs` store; io_uring is Linux-only.
|
|
98
|
+
|
|
99
|
+
Compiling a cache does not replace runtime configuration. `build()` loads
|
|
100
|
+
`goosefs-site.properties` and `GOOSEFS_*` environment variables through
|
|
101
|
+
`GoosefsConfig::from_properties_auto()`:
|
|
102
|
+
|
|
103
|
+
- **Metadata cache**: on by default once `metadata-cache` is compiled. Set
|
|
104
|
+
`GOOSEFS_METADATA_CACHE_ENABLED=false` to disable it.
|
|
105
|
+
- **Page cache**: off by default. Set `GOOSEFS_USER_CLIENT_CACHE_ENABLED=true`
|
|
106
|
+
and configure cache directories through
|
|
107
|
+
`goosefs.user.client.cache.dirs` / `GOOSEFS_USER_CLIENT_CACHE_DIRS`.
|
|
108
|
+
|
|
109
|
+
See [goosefs-sdk client configuration](https://github.com/Tencent/tencent-goosefs-rust-sdk/blob/main/docs/CLIENT_CONFIGURATION.md)
|
|
110
|
+
for the full cache knob list.
|
|
111
|
+
|
|
42
112
|
## Example
|
|
43
113
|
|
|
44
114
|
### Via Builder
|
|
@@ -140,8 +210,7 @@ This service is covered by all three OpenDAL test layers:
|
|
|
140
210
|
`create_dir`). Start the fixture and point the harness at it:
|
|
141
211
|
|
|
142
212
|
```shell
|
|
143
|
-
# Start a single-container GooseFS (master + worker
|
|
144
|
-
# job_worker + table_master)
|
|
213
|
+
# Start a single-container GooseFS (master + worker; see start-default.sh)
|
|
145
214
|
docker compose -f fixtures/goosefs/docker-compose-goosefs.yml up -d --wait
|
|
146
215
|
|
|
147
216
|
OPENDAL_TEST=goosefs \
|
|
@@ -158,7 +227,7 @@ This service is covered by all three OpenDAL test layers:
|
|
|
158
227
|
3. **GitHub CI** — `.github/services/goosefs/goosefs/action.yml` is picked up
|
|
159
228
|
automatically by `.github/scripts/test_behavior/plan.py::provided_cases()`
|
|
160
229
|
and runs the fixture + behavior suite on every PR. The fixture image
|
|
161
|
-
(`
|
|
230
|
+
(`ghcr.io/tencent/tencent-goosefs-rust-sdk/goosefs:v2.0.0`) is public so no secrets
|
|
162
231
|
are needed.
|
|
163
232
|
|
|
164
233
|
The fixture also exposes a `distributed` compose profile (separate master /
|