ironspdk 0.2.3

ironspdk (Rust runtime for SPDK)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# ironspdk

[![raid1 example CI](https://img.shields.io/github/actions/workflow/status/ironspdk/ironspdk/raid1-simple-test.yml?branch=master&label=raid1%20example%20CI)](https://img.shields.io/github/actions/workflow/status/ironspdk/ironspdk/raid1-simple-test.yml?branch=master&label=raid1%20example%20CI)
[![raid5 example CI](https://img.shields.io/github/actions/workflow/status/ironspdk/ironspdk/raid5-simple-test.yml?branch=master&label=raid5%20example%20CI)](https://img.shields.io/github/actions/workflow/status/ironspdk/ironspdk/raid5-simple-test.yml?branch=master&label=raid5%20example%20CI)
![License](https://img.shields.io/crates/l/ironspdk?style=flat-square)

Rust runtime for [SPDK](https://spdk.io/). Write high-performance userspace storage drivers in Rust.

## Overview

SPDK provides a high-performance userspace storage framework based on
polling and asynchronous I/O. Its native API is written in C and uses
callbacks and explicit resource management.

`ironspdk` provides a Rust interface to this execution model. It uses Rust's
type system and ownership rules to manage resources such as I/O requests,
DMA buffers, and I/O channels, while allowing storage operations to be
implemented using Rust futures.

The runtime is designed to keep the execution model of SPDK rather than
introducing a general-purpose async runtime on top of it.
SPDK's reactor-based execution model is integrated with Rust's asynchronous model.
Rust futures are executed on SPDK threads, and SPDK I/O operations can be composed using
`async`/`await`.

The project is intended for applications where SPDK's userspace storage
architecture is appropriate and where Rust's memory safety and language
features are useful for implementing storage logic.

## Key Features

### 🦀 Idiomatic Rust Programming Model
* **No callback hell**: Use Rust's async/await syntax and futures for natural asynchronous I/O handling.
* **Memory safety**: Leverage Rust's ownership and borrowing system to prevent data races and memory bugs.
* **Type safety**: Compile-time guarantees replace runtime errors.

### âš¡ SPDK Integration
* **Full SPDK primitives support**: SPDK lightweight threads, I/O channels, block device descriptors, and more are exposed to Rust.
* **Tight runtime integration**: The `ironspdk` runtime executor extends the SPDK poller, allowing Rust code to seamlessly integrate with the SPDK event loop.
* **Zero-copy I/O**: Work directly with SPDK I/O vectors and DMA buffers.
  without unnecessary data copies.
* **SPDK thread model**: Build on SPDK's thread-per-core, message-passing
  execution model rather than introducing a conventional shared-state
  threading model.
* **C <-> Rust interoperability**: Use SPDK's C API directly from Rust when the higher-level abstractions are not sufficient.

### 🚀 Performance
* **Native code**: Rust code is compiled to native machine code with the
  same optimization opportunities as other systems languages.
* **Low-overhead abstractions**: Keep the abstractions close to the
  underlying SPDK primitives.
* **Lock-free architecture**: Take advantage of SPDK's thread-per-core model
  to avoid unnecessary shared-state synchronization.
* **Direct FFI binding**: Minimal abstraction over underlying SPDK C APIs.

### 🔀 I/O Abstractions
* **Multiple I/O representations**: Support for borrowed I/O references (`IoRef`), owned I/O buffers (`IoBuf`), and unified `Io` enum.
* **I/O splitting**: Advanced API for splitting and reordering I/O operations.
* **DMA buffers**: Allocate and manage SPDK-compatible DMA memory through
  Rust-owned buffers.
* **Block device abstraction**: Simple trait-based interface for implementing custom block devices.

## Architecture

### Core Components

```
ironspdk-sys/          # Low-level C FFI bindings to SPDK
ironspdk/              # High-level Rust runtime and abstractions
  ├── app.rs           # SpdkApp lifecycle management
  ├── lib.rs           # Core types: Bdev, IoRef, IoBuf, SpdkThread, Tcb
  ├── c.rs             # C FFI wrappers
  ├── c_enum.rs        # Enum conversions
  └── rpc.rs           # RPC command registration
examples/raid1/        # Simple RAID1 implementation example
examples/raid5/        # Minimal reference RAID5 implementation example
```

### Runtime Executor

The `ironspdk` runtime leverages SPDK's poller mechanism:
- **Run queue**: Manages async task execution on each SPDK thread
- **Poller**: Queues and polls futures
- **TLS**: Lightweight per SPDK thread storage (to store contexts, for ex. I/O channels)
- **Waker integration**: Custom waker implementation to notify tasks in runqueue

## Usage

### Basic Setup

Add to your `Cargo.toml`:

```toml
[build-dependencies]
cc = "1.2.56"
ironspdk-sys = "0.2"

[dependencies]
ironspdk-sys = "0.2"
ironspdk = "0.2"
```

### Create a Simple Block Device

Simplified code of `ironspdk` block device:

```rust
use ironspdk::{
    Bdev, BdevIoChannel, BdevIoChannelRef, BdevIo, IoType, SpdkThread,
    RawBdevHandle
};

struct MyBdevIoChannel {
    // I/O channel state (per-io_device-and-spdk_thread)
}

struct MyBdev {
    // Your block device global state, read-only for submit_io threads
}

impl Bdev for MyBdev {
    fn init(&self, rawbdev: RawBdevHandle) {
        // Initialize your block device
    }

    fn io_type_supported(&self, io_type: IoType) -> bool {
        matches!(io_type, IoType::Read | IoType::Write)
    }

    fn create_io_channel(&self) -> Box<BdevIoChannel> {
        // Create and return an I/O channel context
        Box::new(BdevIoChannel::new(MyBdevIoChannel {}))
    }

    fn submit_io(&self, ch: BdevIoChannelRef, io: BdevIo) {
        // Handle I/O requests asynchronously
        SpdkThread::current().spawn(async move {
            // Process I/O...
            io.complete(IoStatus::Success);
        });
    }
}
```

See `examples/` for exact implementations.

### Build & Run

```bash
# Set up environment
export SPDK=/path/to/built/spdk

# Or use local SPDK git submodule dependency
git submodule update --init --recursive # actualize SPDK dependency

# Build
make release

# Run with specific CPU cores (0xf = cores 0-3)
sudo RUST_LOG=info ./target/release/your_app -m 0xf
```

## Examples

### RAID1 Block Device

The repository includes a simple yet functional RAID1 implementation (`examples/raid1/`). This example demonstrates:
- Mirroring I/O across several backend block devices
- Handling read/write operations
- RPC-based management interface

**Compare with SPDK's C implementation `raid1.c`**: The Rust version is significantly more concise and readable, while maintaining almost identical performance.

### RAID5 Block Device

A simple RAID5 implementation (`examples/raid5/`). This example demonstrates:
- Data striping with distributed XOR parity
- Full-stripe writes and partial-stripe writes using read-modify-write (RMW)
- Recovery of a single failed read using parity reconstruction
- Per-stripe request serialization to prevent concurrent RMW operations from corrupting parity
- RPC-based management interface

This is a reference implementation intended to demonstrate RAID5 concepts, not a production-ready solution. It does not provide crash consistency or persistent failure tracking.

#### Running the RAID1 Example

```bash
# Terminal 1: Start the RAID1 driver
cd ironspdk
SPDK=/path/to/built/spdk/
make release
# run RAID1 usermode driver example at 4 CPU cores
sudo RUST_LOG=info ./target/release/raid1 -m 0xf

# Terminal 2: Create backend devices
SPDK=/path/to/built/spdk/
cd $SPDK
sudo ./scripts/rpc.py bdev_malloc_create -b malloc0 64 512
sudo ./scripts/rpc.py bdev_malloc_create -b malloc1 64 512
sudo ./scripts/rpc.py bdev_malloc_create -b malloc2 64 512

# Create RAID1 instance
sudo PYTHONPATH=/path/to/ironspdk/examples/raid1/ ./scripts/rpc.py \
    --plugin raid1 \
    rs_raid1_create --name my_ironspdk_raid1 -c malloc0,malloc1,malloc2

# Export via ublk and benchmark with fio
sudo modprobe ublk_drv
sudo ./scripts/rpc.py ublk_create_target
sudo ./scripts/rpc.py ublk_start_disk my_ironspdk_raid1 1 -q $(nproc) -d 128

# Run I/O benchmark
TIME=30
sudo fio --filename=/dev/ublkb1 --direct=1 --numjobs=$(nproc) \
    --rw=randrw --bs=4096 --iodepth=32 --ioengine=libaio \
    --time_based=1 --runtime=$TIME --name=raid1_test

# Cleanup
sudo ./scripts/rpc.py ublk_stop_disk 1
sudo PYTHONPATH=/path/to/ironspdk/ ./scripts/rpc.py \
    --plugin ironspdk rs_bdev_delete my_ironspdk_raid1
sudo ./scripts/rpc.py bdev_malloc_delete malloc2
sudo ./scripts/rpc.py bdev_malloc_delete malloc1
sudo ./scripts/rpc.py bdev_malloc_delete malloc0
```

#### Running the RAID5 Example

This is similar to RAID1. The block device creation command is:

```
sudo PYTHONPATH=/path/to/ironspdk/examples/raid5/ ./scripts/rpc.py \
    --plugin raid5 \
    rs_raid5_create --name my_ironspdk_raid5 -z 16 -c malloc0,malloc1,malloc2
```

## API Overview

### Core Types

#### `SpdkApp`
Main application entry point. Manages SPDK initialization, thread creation, and lifecycle.

```rust
let mut app = SpdkApp::new("my_app");
app.on_start(|| { /* startup code */ });
app.on_shutdown(|| { /* shutdown code */ });
app.run()?;
```

#### `SpdkThread`
Wrapper around SPDK threads. Enables spawning async tasks and inter-thread communication.

```rust
// create new SPDK thread at core 2
let thread = SpdkThread::new_at_cores("my_thread", [2]);

// run some code at this SPDK thread asynchronously
thread.spawn(async { /* async work */ });

// stop SPDK threads this way only
thread.request_exit();
```

#### `Bdev` (Trait)
Implement this trait to create custom block devices.

```rust
pub trait Bdev {
    fn init(&self, ctx: RawBdevHandle);
    fn io_type_supported(&self, io_type: IoType) -> bool;
    fn create_io_channel(&self) -> Box<BdevIoChannel>;
    fn submit_io(&self, ch: BdevIoChannelRef, io: BdevIo);
}
```

#### `BdevIo`
Represents a single I/O request. Provides access to request metadata and completion mechanism.

```rust
pub struct BdevIo { /* ... */ }

impl BdevIo {
    pub fn io_type(&self) -> IoType;
    pub fn offset_blocks(&self) -> u64;
    pub fn num_blocks(&self) -> u64;
    pub fn block_len(&self) -> usize;
    pub fn range(&self) -> Option<IoRange>;
    pub fn complete(&self, status: IoStatus);
    pub fn complete_on(self, thread: &SpdkThread, status: IoStatus);
}
```

#### `Io<'a>` (Enum)
Unified interface for working with I/O data. Can be either a reference to SPDK I/O vectors or a buffered copy.

```rust
pub enum Io<'a> {
    Ref(IoRef<'a>),    // Zero-copy reference to SPDK buffers
    Buf(IoBuf),        // Copy to/from DMA buffer
}

impl<'a> Io<'a> {
    pub fn iter_iov(&self) -> IoIter;                    // Iterate over buffers
    pub fn iter_iov_mut(&mut self) -> IoIterMut;         // Mutable iteration
    pub fn split(&'a self, child_block_len: Option<usize>)
        -> Result<IoRefSplitter<'a>, Error>;             // Split I/O operations
    pub fn offset_blocks(&self) -> u64;
    pub fn num_blocks(&self) -> usize;
}
```

#### `DmaBuf`
DMA-allocated memory buffer.
It may be shared between threads, so it implements Send+Sync.

```rust
pub struct DmaBuf { /* ... */ }

impl DmaBuf {
    pub fn new(len: usize, align: usize) -> Result<Self, Error>;
    pub fn new_aligned(len: usize, align: usize) -> Result<Self, Error>;
    pub fn new_zeroed(len: usize) -> Result<Self, Error>;
    pub fn new_aligned_zeroed(len: usize, align: usize) -> Result<Self, Error>;
    pub fn as_slice(&self) -> &[u8];
    pub fn as_mut_slice(&mut self) -> &mut [u8];
}
```

#### `Lbdev`
Client API for accessing lower-layer SPDK block devices.

```rust
pub struct Lbdev { /* ... */ }

impl Lbdev {
    pub fn open(name: &str) -> Result<Self, Error>;
    pub fn desc(&self) -> &BdevDesc;
    pub fn get_io_channel(&self) -> Rc<LbdevIoChannel>;
    pub fn read<'ctx, 'io>(
        &self,
        ch: &LbdevIoChannel,
        io: &mut Io<'io>,
        ctx: &'ctx mut LbdevIoCtx,
    ) -> LbdevIoFuture<'ctx>;
    pub fn write<'ctx, 'io>(
        &self,
        ch: &LbdevIoChannel,
        io: &mut Io<'io>,
        ctx: &'ctx mut LbdevIoCtx,
    ) -> LbdevIoFuture<'ctx>;
}
```

### Error Handling

All fallible operations return `Result<T, Error>`. The `Error` enum covers common SPDK scenarios:

```rust
pub enum Error {
    AlreadyExists,
    SpdkBdevNotFound(String),
    SpdkBdevCreate(i32),
    SpdkBdevOpen(i32),
    NoMemory,
    UnsupportedFeature,
    SharedBufferModification,
    // ... and more
}
```

## Requirements

* **Rust**: 1.70+
* **SPDK**: Built and configured (see [SPDK documentation]https://spdk.io/), version v26.01 is supported
* **Linux**: confirmed support at 6.17+ kernels
* **Privileges**: Most operations require superuser access for hardware access and memory management

## Performance Characteristics

- **Latency**: Microsecond-scale I/O latency (same as C SPDK)
- **Throughput**: Limited only by underlying hardware (Rust overhead is minimal)
- **CPU efficiency**: Lock-free design with thread-per-core scaling
- **Memory**: Minimal overhead compared to C implementation

## Licensing

Licensed under either of:

* Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE)
* BSD 3-Clause License ([LICENSE-BSD-3-Clause]LICENSE-BSD-3-Clause)

at your option.

## Contributing

Contributions are welcome! Please:

1. Ensure all tests pass: `cargo test`
2. Format code: `cargo fmt --`
3. Run clippy: `cargo clippy --locked --all --all-targets --tests -- -D warnings`
4. Document public APIs
5. Add tests for new functionality

## Getting Help

* **SPDK Documentation**: [https://spdk.io/doc/]https://spdk.io/doc/
* **Rust async/await**: [https://rust-lang.github.io/async-book/]https://rust-lang.github.io/async-book/
* **Repository Issues**: Open an issue on GitHub for bugs or feature requests

## Roadmap

- [x] More public API documentation
- [x] Documentation at docs.rs
- [x] RAID5 example
- [ ] Test coverage (cargo test)
- [ ] Additional block device examples
- [ ] T10 PI (DIF/DIX) support
- [ ] SPDK bdev resizing support
- [ ] Performance profiling tools
- [ ] Higher-level storage abstractions
- [ ] FreeBSD support

## Related or Similar Projects

* [SPDK]https://spdk.io/ - Storage Performance Development Kit
* [Tokio]https://tokio.rs/ - Async Rust runtime
* [Rust for Linux]https://github.com/Rust-for-Linux/linux - Bringing Rust to kernel space