Skip to content

Commit

Permalink
Try #1771:
Browse files Browse the repository at this point in the history
  • Loading branch information
mayastor-bors committed Nov 25, 2024
2 parents 2825e5e + 1dac4b4 commit 0ffe524
Show file tree
Hide file tree
Showing 10 changed files with 510 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion doc/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ cargo build --release
```
**Want to run or hack on Mayastor?** _You need more configuration!_ See
[running][doc-running], then [testing][doc-testing].
[running][doc-run], then [testing][doc-test].
Whilst the nix develop will allow you to build mayastor exactly as the image build, it might not have all the necessary
components required for testing.
Expand Down
29 changes: 28 additions & 1 deletion doc/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ Mayastor's unit tests, integration tests, and documentation tests via the conven
Mayastor uses [spdk][spdk] which is quite senistive to threading. This means tests need to run one at a time:

```bash
cargo test -- --test-threads 1
cd io-engine
RUST_LOG=TRACE cargo test -- --test-threads 1 --nocapture
```

## Testing your own SPDK version
To test your custom SPDK version please refere to the [spdk-rs documentation](https://github.com/openebs/spdk-rs/blob/develop/README.md#custom-spdk)

## Running the end-to-end test suite

Mayastor does more complete, end-to-end testing testing with [`mocha`][mocha]. It requires some extra setup.
Expand All @@ -55,6 +59,29 @@ Then, to run the tests:
./node_modules/mocha/bin/mocha test_csi.js
```

## Using PCIe NVMe devices in cargo tests while developing

When developing new features, testing those with real PCIe devices in the process might come in handy.
In order to do so, the PCIe device first needs to be bound to the vfio driver:

```bash
sudo PCI_ALLOWED="<PCI-ADDRESS>" ./spdk-rs/spdk/scripts/setup.sh
```

The bdev name in the cargo test case can then follow the PCIe URI pattern:

```rust
static BDEVNAME1: &str = "pcie:///<PCI-ADDRESS>";
```

After testing the device may be rebound to the NVMe driver:

```bash
sudo PCI_ALLOWED="<PCI-ADDRESS>" ./spdk-rs/spdk/scripts/setup.sh reset
```

Please do not submit pull requests with active cargo test cases that require PCIe devices to be present.

[spdk]: https://spdk.io/
[doc-run]: ./run.md
[mocha]: https://mochajs.org/
Expand Down
2 changes: 2 additions & 0 deletions io-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extended-tests = [] # Extended I/O engine tests: not intended for daily runs.
fault-injection = [] # Enables fault injection code.
nexus-io-tracing = [] # Enables nexus I/O tracing code.
spdk-async-qpair-connect = [] # Enables async qpair connection.
nvme-pci-tests = []

[[bin]]
name = "io-engine"
Expand Down Expand Up @@ -77,6 +78,7 @@ merge = "0.1.0"
nix = { version = "0.29.0", default-features = false, features = ["hostname", "net", "socket", "ioctl"] }
once_cell = "1.20.2"
parking_lot = "0.12.3"
percent-encoding = "2.3.1"
pin-utils = "0.1.0"
prost = "0.13.3"
prost-derive = "0.13.3"
Expand Down
2 changes: 2 additions & 0 deletions io-engine/src/bdev/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub(crate) mod uri {
use crate::{
bdev::{
aio,
ftl,
loopback,
lvs,
malloc,
Expand All @@ -64,6 +65,7 @@ pub(crate) mod uri {
"bdev" | "loopback" => {
Ok(Box::new(loopback::Loopback::try_from(&url)?))
}
"ftl" => Ok(Box::new(ftl::Ftl::try_from(&url)?)),
"malloc" => Ok(Box::new(malloc::Malloc::try_from(&url)?)),
"null" => Ok(Box::new(null_bdev::Null::try_from(&url)?)),
// keeping nvmf scheme so existing tests(if any, setting this
Expand Down
292 changes: 292 additions & 0 deletions io-engine/src/bdev/ftl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
//! The Ftl device is a representation of an SPDK (CSAL) ftl bdev, which allows
//! to create a layered device with a persistent (fast) cache device for
//! buffering writes that get eventually flushed out sequentially to a base
//! device.
//!
//! # Uri
//! ftl:///$ftl_device_name?bbdev=$bbdev_uri_percent_encoded&cbdev=$cbdev_uri_percent_encoded
//!
//! The bbdev_uri and cbdev_uri need to use percent encoding on '?' (= '%3F') and '&' (= '%26')
//! segment dividers. This is needed so we can successfully parse the ftl uri. Optionally, the
//! sub-uris may also be fully percent-encoded.
//!
//! # Parameters
//! name: A name for the ftl device, example: "ftl-1".
//! alias: Ftl device URI which can be used to open the bdev.
//! uuid: A UUID that can be seet to reference the resutling SPDK bdev.
//! bbdev_uri: Nested device uri for the ftl base bdev, which is (partially) percent encoded.
//! This device currently requires to be a non-volatile memory device which is has an
//! LBA format of 4KiB and is at least 20GB large.
//! cbdev_uri: Nested device uri for the ftl base bdev, which is (partially) percent encoded.
//! This device currently requires to be a non-volatile memory device which is has an
//! LBA format of 4KiB data size, 64B metadata size and is at least 20GB large.
//! From SPDK v24.09 on there is no requirement for the metadata portion as
//! Variable Sector Size (VSS) emulation is enabled.
//!
//! # Examples
//! ftl:///ftl-1?bbdev=pcie:///0000:01:00.0&cbdev=pcie:///0000:02:00.0
//! ftl:///ftl-1?bbdev=pcie%3A%2F%2F%2F0000%3A01%3A00.0&cbdev=pcie%3A%2F%2F%2F0000%3A02%3A00.0
//! ftl:///ftl-1?bbdev=pcie:///0000:01:00.0%3Finvalidoption=test&cbdev=pcie:///0000:02:00.0
//!
//! ftl:///ftl-2?bbdev=aio:///tmp/basedev.img%3Fblk_size=4096&cbdev=aio:///tmp/cachedev.img%3Fblk_size=4096
use std::{
collections::HashMap,
convert::TryFrom,
fmt::{Debug, Formatter},
};

use core::ffi::c_void;

use async_trait::async_trait;
use futures::channel::oneshot;
use log::info;
use nix::errno::Errno;
use percent_encoding::percent_decode_str;
use snafu::{OptionExt, ResultExt};
use std::mem;
use url::Url;

use spdk_rs::{
ffihelper::errno_result_from_i32,
libspdk::{
bdev_ftl_create_bdev, bdev_ftl_delete_bdev, ftl_bdev_info,
spdk_ftl_conf, spdk_ftl_get_default_conf, spdk_ftl_mode,
},
UntypedBdev,
};

use crate::{
bdev::{dev::reject_unknown_parameters, util::uri, CreateDestroy, GetName},
bdev_api::{self, bdev_create, bdev_destroy, BdevError},
core::VerboseError,
ffihelper::{cb_arg, done_errno_cb, ErrnoResult},
};

/// An ftl bdev specified via URI.
pub struct Ftl {
/// The name of the ftl-bdev we created.
name: String,
/// Alias which can be used to open the bdev.
alias: String,
/// Uuid of the spdk bdev.
uuid: Option<uuid::Uuid>,
/// Ftl's base bdev URI.
bbdev_uri: String,
/// Ftl's cache bdev URI.
cbdev_uri: String,
}

impl Debug for Ftl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Ftl '{}' (bbdev_uri {} cbdev_uri {})",
self.name, self.bbdev_uri, self.cbdev_uri
)
}
}

impl TryFrom<&Url> for Ftl {
type Error = BdevError;

fn try_from(uri: &Url) -> Result<Self, Self::Error> {
let segments = uri::segments(uri);
if segments.is_empty() {
return Err(BdevError::InvalidUri {
uri: uri.to_string(),
message: "empty path".to_string(),
});
}

let mut parameters: HashMap<String, String> =
uri.query_pairs().into_owned().collect();

let uuid = uri::uuid(parameters.remove("uuid")).context(
bdev_api::UuidParamParseFailed {
uri: uri.to_string(),
},
)?;

let encoded_bbdev_uri =
parameters.remove("bbdev").context(bdev_api::InvalidUri {
uri: uri.to_string(),
message: String::from("No bbdev parameter found"),
})?;

let bbdev_uri = percent_decode_str(&encoded_bbdev_uri)
.decode_utf8()
.map_err(|e| BdevError::InvalidUri {
uri: uri.to_string(),
message: format!(
"Could not percent decode bbdev_uri sub-uri - {}",
e.to_string()
),
})?
.to_string();

let encoded_cbdev_uri =
parameters.remove("cbdev").context(bdev_api::InvalidUri {
uri: uri.to_string(),
message: String::from("No cbdev parameter found"),
})?;

let cbdev_uri = percent_decode_str(&encoded_cbdev_uri)
.decode_utf8()
.map_err(|e| BdevError::InvalidUri {
uri: uri.to_string(),
message: format!(
"Could not percent decode cbdev_uri sub-uri - {}",
e.to_string()
),
})?
.to_string();

// Device parameter checking for bbdev and cbdev are done in bdev_ftl_create_bdev
reject_unknown_parameters(uri, parameters)?;

Ok(Self {
name: uri.path()[1..].into(),
alias: uri.to_string(),
uuid,
bbdev_uri,
cbdev_uri,
})
}
}

impl GetName for Ftl {
fn get_name(&self) -> String {
self.name.clone()
}
}

pub extern "C" fn ftl_bdev_init_fn_cb(
_ptr: *const ftl_bdev_info,
sender_ptr: *mut c_void,
errno: i32,
) {
info!("{:?}: ftl_bdev_init_fn_cb", errno);
let sender = unsafe {
Box::from_raw(sender_ptr as *mut oneshot::Sender<ErrnoResult<()>>)
};
sender
.send(errno_result_from_i32((), errno))
.expect("done callback receiver side disappeared");
}

#[async_trait(?Send)]
impl CreateDestroy for Ftl {
type Error = BdevError;

async fn create(&self) -> Result<String, Self::Error> {
if UntypedBdev::lookup_by_name(&self.name).is_some() {
return Err(BdevError::BdevExists {
name: self.name.clone(),
});
}

let ftl_dev_name = CString::new(self.name.clone()).unwrap();

debug!(
"{:?}: Creating ftl-bdev '{:?}' with bbdev {:?} and cbdev {:?}.",
self, ftl_dev_name, self.bbdev_uri, self.cbdev_uri
);

use std::ffi::CString;

let base_dev_name = bdev_create(&self.bbdev_uri).await?;
let cache_dev_name = bdev_create(&self.cbdev_uri).await?;

let (s, r) = oneshot::channel::<ErrnoResult<()>>();

let spdk_ftl_conf_size = mem::size_of::<spdk_ftl_conf>() as u64;
let mut ftl_conf = spdk_ftl_conf {
..unsafe { mem::zeroed() }
};
unsafe { spdk_ftl_get_default_conf(&mut ftl_conf, spdk_ftl_conf_size) };
ftl_conf.name = ftl_dev_name.as_ptr() as *mut i8;
ftl_conf.base_bdev = base_dev_name.as_ptr() as *mut i8;
ftl_conf.cache_bdev = cache_dev_name.as_ptr() as *mut i8;
ftl_conf.fast_shutdown = true;
ftl_conf.verbose_mode = true;
ftl_conf.mode = spdk_ftl_mode::SPDK_FTL_MODE_CREATE as u32;

let errno = unsafe {
bdev_ftl_create_bdev(
&mut ftl_conf,
Some(ftl_bdev_init_fn_cb),
cb_arg(s),
)
};

if errno != 0 {
let err = BdevError::CreateBdevFailed {
source: Errno::from_raw(errno.abs()),
name: self.name.clone(),
};

error!("{:?} error: {}", self, err.verbose());

return Err(err);
}

r.await
.context(bdev_api::BdevCommandCanceled {
name: self.name.clone(),
})?
.context(bdev_api::CreateBdevFailed {
name: self.name.clone(),
})?;

if let Some(mut bdev) = UntypedBdev::lookup_by_name(&self.name) {
if let Some(uuid) = self.uuid {
unsafe { bdev.set_raw_uuid(uuid.into()) };
}

if !bdev.add_alias(&self.alias) {
warn!("{:?}: failed to add alias '{}'", self, self.alias);
}

return Ok(self.get_name());
}

Err(BdevError::BdevNotFound {
name: self.get_name(),
})
}

async fn destroy(self: Box<Self>) -> Result<(), Self::Error> {
debug!("{:?}: deleting", self);

let Some(mut bdev) = UntypedBdev::lookup_by_name(&self.name) else {
return Err(BdevError::BdevNotFound { name: self.name });
};

bdev.remove_alias(&self.alias);
let (s, r) = oneshot::channel::<ErrnoResult<()>>();

unsafe {
bdev_ftl_delete_bdev(
(*bdev.unsafe_inner_ptr()).name,
true,
Some(done_errno_cb),
cb_arg(s),
);
}

r.await
.context(bdev_api::BdevCommandCanceled {
name: self.name.clone(),
})?
.context(bdev_api::DestroyBdevFailed { name: self.name })?;

let mut result = bdev_destroy(&self.bbdev_uri).await;

if let Err(e) = bdev_destroy(&self.cbdev_uri).await {
if result.is_ok() {
result = Err(e);
}
}
result
}
}
1 change: 1 addition & 0 deletions io-engine/src/bdev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) use dev::uri;
pub(crate) mod device;
mod loopback;
mod lvs;
mod ftl;
mod malloc;
pub mod nexus;
mod null_bdev;
Expand Down
Loading

0 comments on commit 0ffe524

Please sign in to comment.