Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
6679a19
feat(storage): Add HDFS support via opendal services-hdfs-native
mixermt Aug 29, 2026
4e0f745
Merge branch 'main' into feat-storage-hdfs-native
mixermt Aug 31, 2026
2b0ca95
refactor: address review — Properties derive for HdfsConfig, drop HDF…
mixermt Aug 31, 2026
7939b68
Merge remote-tracking branch 'origin/feat-storage-hdfs-native' into f…
mixermt Aug 31, 2026
45bd68a
fix: reject non-hierarchical hdfs URLs; key delete_stream batches by …
mixermt Aug 31, 2026
4141f4e
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 1, 2026
2578c03
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 3, 2026
13911c5
Merge remote-tracking branch 'apache/main' into feat-storage-hdfs-native
mixermt Sep 5, 2026
a4178fe
refactor(storage): rename hdfs module to hdfs_native, drop unused Hdf…
mixermt Sep 5, 2026
a9a6786
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 8, 2026
806bd89
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 12, 2026
9d7d2d8
ci: retrigger after setup-python manifest timeout on macos runner
mixermt Sep 13, 2026
c561cfc
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 16, 2026
d17ae38
fix(storage): address review — cache newtype, build outside lock, sin…
mixermt Sep 17, 2026
b8cc860
chore(deps): bump rustls to 0.23.45 (RUSTSEC-2026-0285)
mixermt Sep 17, 2026
587688c
Merge remote-tracking branch 'apache/main' into feat-storage-hdfs-native
mixermt Sep 18, 2026
72305b6
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 22, 2026
f8b6803
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 25, 2026
ffee268
Merge branch 'main' into feat-storage-hdfs-native
mixermt Sep 28, 2026
eeca0d1
Merge branch 'main' into feat-storage-hdfs-native
mixermt Oct 1, 2026
2068e83
Merge remote-tracking branch 'apache/main' into feat-storage-hdfs-native
mixermt Oct 3, 2026
297afd6
test(storage): pin IPv6 authority handling in HDFS path parsing
mixermt Oct 3, 2026
c018cd1
fix(storage): resolve the HDFS NameNode in relativize_path too
mixermt Oct 3, 2026
556a0f2
docs(storage): state the cost of the synchronous HDFS operator build
mixermt Oct 3, 2026
d3ef172
docs(storage): describe the HDFS operator cache lifecycle
mixermt Oct 3, 2026
62ea36d
fix(storage): normalize each entry of the hdfs.name-node list
mixermt Oct 3, 2026
3ab457a
feat(storage): accept PyIceberg's hdfs.host/hdfs.port for authority-l…
mixermt Oct 3, 2026
74bb9b1
fix(storage): require a port in every hdfs.name-node entry
mixermt Oct 3, 2026
0928265
docs(storage): state the runtime binding of cached HDFS operators
mixermt Oct 3, 2026
089731f
test(storage): cover two-NameNode delete_stream batches and first-ins…
mixermt Oct 3, 2026
22db8e6
test(utils): name the HDFS port constant like its siblings
mixermt Oct 3, 2026
2f6a57d
Merge remote-tracking branch 'apache/main' into feat-storage-hdfs-native
mixermt Oct 6, 2026
629cfc8
fix(storage): rebuild cached HDFS operators once their runtime is gone
mixermt Oct 6, 2026
e38bab2
fix(storage): resolve HDFS NameNodes like Hadoop does
mixermt Oct 6, 2026
cfe4ee7
refactor(storage): keep the HDFS operator cache out of the public API
mixermt Oct 6, 2026
6603348
perf(storage): build HDFS operators on a blocking thread
mixermt Oct 6, 2026
ed23ab6
ci: run the HDFS integration tests in their own job
mixermt Oct 6, 2026
cc9dd91
fix(storage): stop dropping HDFS properties silently
mixermt Oct 6, 2026
f83db4e
test(storage): derive the second NameNode spelling from the endpoint …
mixermt Oct 6, 2026
9c62f4c
chore: record tracing as a dependency of iceberg-storage-opendal in C…
mixermt Oct 6, 2026
a0d2194
fix(storage): report a missing tokio runtime instead of panicking in …
mixermt Oct 6, 2026
ca92240
fix(storage): normalize every HDFS NameNode spelling to hdfs://host:port
mixermt Oct 6, 2026
ce78ee3
feat(storage): declare HDFS nameservices with hdfs.name-node.<nameser…
mixermt Oct 6, 2026
4a9cc25
fix(storage): require a declaration for every logical HDFS nameservice
mixermt Oct 6, 2026
5870769
fix(storage): reject userinfo and port 0 in HDFS path authorities
mixermt Oct 6, 2026
b72d795
fix(storage): validate the NameNode composed from hdfs.host and hdfs.…
mixermt Oct 6, 2026
7f2dc7f
test(storage): use an authority-less path in the cross-runtime HDFS test
mixermt Oct 6, 2026
b962408
test(storage): tighten the HDFS tests after review
mixermt Oct 6, 2026
ef451b6
Merge branch 'main' into feat-storage-hdfs-native
mixermt Oct 7, 2026
1276d2d
fix(storage): mask userinfo in HDFS errors that echo a path or NameNode
mixermt Oct 8, 2026
1fd3492
fix(storage): recover a poisoned HDFS operator cache lock
mixermt Oct 8, 2026
894a8ed
test(storage): flush the fake NameNode before sampling dials
mixermt Oct 8, 2026
30d74a8
docs(storage): state where portless HDFS authorities differ from Hadoop
mixermt Oct 8, 2026
86d81b4
refactor(storage): rename the HDFS config module to hdfs_native
mixermt Oct 9, 2026
a347b84
test(storage): move the HDFS tests out of lib.rs into hdfs_native
mixermt Oct 9, 2026
39b3cc5
refactor(storage): read HDFS properties through #[derive(Properties)]
mixermt Oct 9, 2026
4ab34ee
ci: run the HDFS tests in the default suite like the other fixtures
mixermt Oct 9, 2026
0bf5d5a
fix(storage): build HDFS clients in a crate-owned runtime
mixermt Oct 9, 2026
f092ea2
refactor(storage): type HDFS path authorities and validate declaratio…
mixermt Oct 9, 2026
26aba03
refactor(storage): drop index slicing and combinator chains in the HD…
mixermt Oct 9, 2026
80f2149
fix(storage): attach the hdfs.port parse error as the source
mixermt Oct 9, 2026
323c94b
fix(storage): share one HDFS client across host name spellings
mixermt Oct 9, 2026
63dbed2
docs(storage): note the upstream gap behind the HDFS client runtime
mixermt Oct 9, 2026
7ac1f45
test(storage): make the HDFS HA test fail over for real
mixermt Oct 9, 2026
fdd9562
Merge apache/main into feat-storage-hdfs-native
mixermt Oct 9, 2026
8aba12b
chore: retrigger CI
mixermt Oct 9, 2026
650e793
chore: retrigger CI
mixermt Oct 10, 2026
0145ca6
docs(storage): document how to enable and configure the HDFS backend
mixermt Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 308 additions & 12 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions crates/iceberg/public-api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,10 @@ pub const iceberg::io::GCS_PROJECT_ID: &str
pub const iceberg::io::GCS_SERVICE_HOST: &str
pub const iceberg::io::GCS_TOKEN: &str
pub const iceberg::io::GCS_USER_PROJECT: &str
pub const iceberg::io::HDFS_HADOOP_CONF_PREFIX: &str
pub const iceberg::io::HDFS_HOST: &str
pub const iceberg::io::HDFS_NAME_NODE: &str
pub const iceberg::io::HDFS_PORT: &str
pub const iceberg::io::HF_ENDPOINT: &str
pub const iceberg::io::HF_REVISION: &str
pub const iceberg::io::HF_TOKEN: &str
Expand Down
45 changes: 45 additions & 0 deletions crates/iceberg/src/io/storage/config/hdfs_native.rs
Comment thread
mixermt marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

//! HDFS storage configuration.

/// HDFS NameNode RPC endpoint(s) as `host:port`, e.g. `hdfs://namenode:8020`;
/// a comma-separated list enables HA failover. Serves authority-less paths
/// (`hdfs:///path`) only; a path authority with a port is used as is. A
/// portless authority (`hdfs://ns1/path`) must be a nameservice declared in
/// properties: `hdfs.name-node.<nameservice>` takes the same list and is
/// sugar for Hadoop's `dfs.ha.namenodes.<nameservice>` and
/// `dfs.namenode.rpc-address.<nameservice>.<id>`, which are honored as well
/// when passed through [`HDFS_HADOOP_CONF_PREFIX`]. Unlike Hadoop, an
/// undeclared one is an error: it is neither resolved from
/// `$HADOOP_CONF_DIR` nor dialed on the default port 8020.
pub const HDFS_NAME_NODE: &str = "hdfs.name-node";
Comment thread
mixermt marked this conversation as resolved.
/// NameNode host for authority-less paths, as in PyIceberg; paths that carry
/// an authority ignore it, as they do there. Combined with [`HDFS_PORT`] into
/// Hadoop's `fs.defaultFS`. PyIceberg's `hdfs.user` and `hdfs.kerberos_ticket`
/// have no equivalent: the client reads `HADOOP_USER_NAME` and the default
/// Kerberos credential cache.
pub const HDFS_HOST: &str = "hdfs.host";
/// NameNode port for [`HDFS_HOST`]; defaults to `8020` and has no effect
/// without the host.
pub const HDFS_PORT: &str = "hdfs.port";
/// Prefix for properties forwarded to the HDFS client configuration with the
/// prefix stripped: `hadoop.dfs.client.failover.random.order` is forwarded as
/// `dfs.client.failover.random.order`. Forwarded values override those loaded
/// from `$HADOOP_CONF_DIR`; `hadoop.fs.defaultFS` also serves authority-less
/// paths.
pub const HDFS_HADOOP_CONF_PREFIX: &str = "hadoop.";
Comment thread
mixermt marked this conversation as resolved.
2 changes: 2 additions & 0 deletions crates/iceberg/src/io/storage/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

mod azdls;
mod gcs;
mod hdfs_native;
mod hf;
mod oss;
mod s3;
Expand All @@ -40,6 +41,7 @@ use std::collections::HashMap;

pub use azdls::*;
pub use gcs::*;
pub use hdfs_native::*;
pub use hf::*;
pub use oss::*;
pub use s3::*;
Expand Down
7 changes: 7 additions & 0 deletions crates/storage/opendal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ opendal-all = [
opendal-azdls = ["opendal/services-azdls"]
opendal-fs = ["opendal/services-fs"]
opendal-gcs = ["opendal/services-gcs"]
opendal-hdfs-native = [
"opendal/services-hdfs-native",
"dep:tokio",
"dep:tracing",
]
opendal-hf = ["opendal/services-hf"]
opendal-memory = ["opendal/services-memory"]
opendal-oss = ["opendal/services-oss"]
Expand All @@ -59,6 +64,8 @@ opendal = { workspace = true }
reqsign-aws-v4 = { version = "3.0.0", optional = true }
reqsign-core = { version = "3.0.0", optional = true }
serde = { workspace = true }
tokio = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
typetag = { workspace = true }
url = { workspace = true }

Expand Down
40 changes: 40 additions & 0 deletions crates/storage/opendal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ OpenDAL-based storage backend implementations for [Apache Iceberg Rust](https://
| Hugging Face | `opendal-hf` | ✅ Stable | Hugging Face buckets and repositories |
| Alibaba Cloud OSS | `opendal-oss` | 🧪 Experimental | Alibaba Cloud Object Storage Service |
| Azure Datalake | `opendal-azdls` | 🧪 Experimental | Azure Datalake Storage v2 |
| HDFS | `opendal-hdfs-native` | 🧪 Experimental | HDFS via the native Rust client (`hdfs-native`) |

You can enable all stable storage backends at once using the `opendal-all` feature flag.

> Note that `opendal-oss` and `opendal-azdls` are currently experimental and not included in `opendal-all`.

> `opendal-hdfs-native` is experimental and not included in `opendal-all`; see [HDFS](#hdfs).

## Usage

Add the crate to your `Cargo.toml` with the feature flags for the backends you need:
Expand Down Expand Up @@ -82,3 +85,40 @@ async fn main() -> iceberg::Result<()> {
Ok(())
}
```

## HDFS

The `opendal-hdfs-native` feature adds an HDFS backend built on
[`hdfs-native`](https://github.com/Kimahriman/hdfs-native), a native Rust client that needs no
JVM. Enable it on its own, since it is not part of `opendal-all`:

```toml
iceberg-storage-opendal = { version = "x.y.z", features = ["opendal-hdfs-native"] }
```

Then pass `OpenDalStorageFactory::HdfsNative` to your catalog builder, or use
`OpenDalResolvingStorageFactory`, which routes `hdfs://` paths to it. The backend reads these
properties:

| Property | Description |
| --- | --- |
| `hdfs.name-node` | NameNode `host:port` for authority-less paths (`hdfs:///warehouse/...`); a comma-separated list enables HA failover |
| `hdfs.name-node.<nameservice>` | The NameNodes of a nameservice used as a path authority (`hdfs://nameservice1/warehouse/...`), comma-separated |
| `hdfs.host`, `hdfs.port` | PyIceberg's keys for authority-less paths; the port defaults to `8020` |
| `hadoop.*` | Forwarded to the HDFS client with the prefix stripped, e.g. `hadoop.dfs.client.failover.max.attempts` |

A path with a `host:port` authority is always used as is. For a table under
`hdfs://nameservice1/warehouse` on an HA cluster:

```text
hdfs.name-node.nameservice1 = nn1.example.com:8020,nn2.example.com:8020
```

The client also loads `core-site.xml` and `hdfs-site.xml` from `$HADOOP_CONF_DIR`, else
`$HADOOP_HOME/etc/hadoop`, and takes the user from `HADOOP_USER_NAME`. Kerberos is used when
`hadoop.security.authentication` is `kerberos` there, with the default credential cache
(`kinit`); `libgssapi_krb5` is loaded at runtime. PyIceberg's `hdfs.user` and
`hdfs.kerberos_ticket` are ignored with a warning.

Unlike Hadoop, a portless authority is never resolved from `$HADOOP_CONF_DIR` or dialed on
port 8020: declare it with `hdfs.name-node.<nameservice>`.
11 changes: 11 additions & 0 deletions crates/storage/opendal/public-api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub iceberg_storage_opendal::OpenDalStorage::Azdls::config: alloc::sync::Arc<ope
pub iceberg_storage_opendal::OpenDalStorage::Gcs
pub iceberg_storage_opendal::OpenDalStorage::Gcs::client_config: iceberg_storage_opendal::OpenDalClientConfig
pub iceberg_storage_opendal::OpenDalStorage::Gcs::config: alloc::sync::Arc<opendal_service_gcs::config::GcsConfig>
pub iceberg_storage_opendal::OpenDalStorage::HdfsNative(iceberg_storage_opendal::HdfsNativeStorage)
pub iceberg_storage_opendal::OpenDalStorage::Hf
pub iceberg_storage_opendal::OpenDalStorage::Hf::client_config: iceberg_storage_opendal::OpenDalClientConfig
pub iceberg_storage_opendal::OpenDalStorage::Hf::config: alloc::sync::Arc<opendal_service_hf::config::HfConfig>
Expand Down Expand Up @@ -47,6 +48,7 @@ pub enum iceberg_storage_opendal::OpenDalStorageFactory
pub iceberg_storage_opendal::OpenDalStorageFactory::Azdls
pub iceberg_storage_opendal::OpenDalStorageFactory::Fs
pub iceberg_storage_opendal::OpenDalStorageFactory::Gcs
pub iceberg_storage_opendal::OpenDalStorageFactory::HdfsNative
pub iceberg_storage_opendal::OpenDalStorageFactory::Hf
pub iceberg_storage_opendal::OpenDalStorageFactory::Memory
pub iceberg_storage_opendal::OpenDalStorageFactory::Oss
Expand All @@ -69,6 +71,15 @@ impl core::clone::Clone for iceberg_storage_opendal::CustomAwsCredentialLoader
pub fn iceberg_storage_opendal::CustomAwsCredentialLoader::clone(&self) -> Self
impl core::fmt::Debug for iceberg_storage_opendal::CustomAwsCredentialLoader
pub fn iceberg_storage_opendal::CustomAwsCredentialLoader::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct iceberg_storage_opendal::HdfsNativeStorage
impl core::clone::Clone for iceberg_storage_opendal::HdfsNativeStorage
pub fn iceberg_storage_opendal::HdfsNativeStorage::clone(&self) -> iceberg_storage_opendal::HdfsNativeStorage
impl core::fmt::Debug for iceberg_storage_opendal::HdfsNativeStorage
pub fn iceberg_storage_opendal::HdfsNativeStorage::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl serde_core::ser::Serialize for iceberg_storage_opendal::HdfsNativeStorage
pub fn iceberg_storage_opendal::HdfsNativeStorage::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde_core::ser::Serializer>::Ok, <__S as serde_core::ser::Serializer>::Error> where __S: serde_core::ser::Serializer
impl<'de> serde_core::de::Deserialize<'de> for iceberg_storage_opendal::HdfsNativeStorage
pub fn iceberg_storage_opendal::HdfsNativeStorage::deserialize<__D>(__deserializer: __D) -> core::result::Result<Self, <__D as serde_core::de::Deserializer>::Error> where __D: serde_core::de::Deserializer<'de>
pub struct iceberg_storage_opendal::OpenDalClientConfig
impl iceberg_storage_opendal::OpenDalClientConfig
pub fn iceberg_storage_opendal::OpenDalClientConfig::from_properties(properties: &std::collections::hash::map::HashMap<alloc::string::String, alloc::string::String>) -> iceberg::error::Result<Self>
Expand Down
Loading
Loading