swiftfuse architecture
Pure-Rust FUSE client for OpenStack Swift. Native Swift v1 API — no S3 translation layer.
┌───────────────────────────────┐
│ swiftfuse (Rust, tokio) │
POSIX │ │
syscalls │ PathFilesystem (fuse3) │
│ │ │ │
▼ │ ├─ attr cache (TTL) │
/dev/fuse ─────►│ ├─ dir cache (TTL) │
│ ├─ open-file handles │
│ │ └─ temp-file write-back │
│ └─ prefetch read window │
│ │ │
│ ▼ │
│ SwiftClient (retry, auth) │
└──────────┬────────────────────┘
│ native Swift v1 HTTP
▼
Swift proxy (LB) → object nodes
Phase 1: thin native adapter (this codebase)
Layout: / = Swift account, /container = container, /container/name =
object. Sub-directories are synthesized from name prefixes (/ separator);
mkdir materializes a zero-byte marker object <dir>/ so empty directories
persist across remounts. Raw objects remain fully visible to native Swift
clients.
Consistency model (close-to-open, GeeseFS/JuiceFS-style)
| Operation | Implementation |
|---|---|
| read | range GETs straight from Swift; per-handle prefetch |
| write | buffered into a local temp file per open handle |
| flush/close | ONE atomic object PUT (≤ 5 GiB) or segment manifest |
| metadata | TTL caches; invalidated on local mutation |
| rename | server-side COPY + DELETE (file); recursive dir copy |
Readers never observe a partially written object: a replace is either the old object or the new one. This is the “objects are immutable, mutation happens in metadata” principle applied at the object granularity.
Large objects
Files above single_put_max are written as immutable segments
(<cont>_segments/<name>.segments/<hash>/<seq>) and published atomically as a
manifest:
- DLO (default): empty object +
X-Object-Manifestheader. Works on every Swift deployment. - SLO: JSON manifest PUT. Requires the cluster’s
slomiddleware; the lab cluster’s SLO middleware is not functional (missing[filter:slo]config was added, still inert), hence DLO default.
Segment names are deterministic (content hash of the object name), so rewrites coalesce and orphaned segments can be garbage-collected by prefix.
Authentication
TempAuthV1:GET /auth/v1.0, X-Auth-User / X-Auth-Key. Storage URL overridable (node-local endpoint instead of VIP).KeystoneV3: password → scoped token; object-store endpoint resolved from the catalog, overridable.- Token cached, proactively refreshed, force-refreshed on 401.
- Idempotent requests retry with exponential backoff; streaming PUTs recreate their body per attempt.
Known limitations (phase 1)
- Partial overwrite of a large existing file materializes the whole object to the temp file first (write amplification, like every object adapter).
- No multi-client locking; last-flush-wins (close-to-open consistency).
- No hard links; symlinks unsupported (ENOSYS).
- xattrs not supported (ENOSYS).
- Orphaned segments from overwritten large objects need GC.
Phase 2 roadmap: chunk/slice/block data plane
The 宗旨 architecture (metadata/data separation, immutable objects, mutation only in metadata) maps onto this codebase as follows:
inode (metadata: size, times, mode) ← SQLite/etcd/TiKV (control plane)
└── chunk 0..N (logical 32–256 MiB) ← fixed-size, like JuiceFS
└── slice (one write op) ← immutable
└── block ≤ 4–16 MiB ← the physical Swift object
- Random 4 KiB write = one new immutable block + atomic metadata commit (never rewrite the object).
- Background compaction merges slices per chunk.
- FUSE layer already routes through
Handle+SloWriter; the chunk plane replaces the temp-file full-buffer path behind the same flush/close hook. - Metadata engine choice for a 4-node lab: SQLite (single mount) or etcd (multi-client leases, ProxyFS-style).
Testing
crates/swift-client/tests/client_test.rs: auth flows, pagination, range GETs, retry/401-refresh (wiremock).- Integration on the cluster:
scripts/smoke_test.sh(POSIX ops + md5 verification + SLO/DLO large files).