use crate::container::{Container, ContainerOpen};
use crate::error::{DaosError, Result};
use crate::io::{AKey, DKey, IoBuffer, Iod, IodSingleBuilder, Sgl};
use crate::object::{
Object, ObjectClass, ObjectClassHints, ObjectId, ObjectOpenMode, ObjectType, generate_oid,
};
use crate::pool::{Pool, PoolBuilder};
use crate::runtime::DaosRuntime;
use crate::tx::Tx;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FacadeError {
NotConnected,
InvalidConfig(String),
Daos(DaosError),
}
impl std::fmt::Display for FacadeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FacadeError::NotConnected => write!(f, "client not connected"),
FacadeError::InvalidConfig(msg) => write!(f, "invalid configuration: {}", msg),
FacadeError::Daos(e) => write!(f, "DAOS error: {}", e),
}
}
}
impl std::error::Error for FacadeError {}
impl From<DaosError> for FacadeError {
fn from(err: DaosError) -> Self {
FacadeError::Daos(err)
}
}
impl From<FacadeError> for DaosError {
fn from(err: FacadeError) -> Self {
match err {
FacadeError::Daos(e) => e,
FacadeError::NotConnected => DaosError::InvalidArg,
FacadeError::InvalidConfig(_) => DaosError::InvalidArg,
}
}
}
#[derive(Debug)]
pub struct DaosClientBuilder {
pool_label: Option<String>,
pool_uuid: Option<String>,
pool_sys: Option<String>,
pool_flags: u32,
container_label: Option<String>,
container_uuid: Option<String>,
container_flags: u32,
object_type: ObjectType,
object_class: ObjectClass,
object_hints: ObjectClassHints,
}
impl Default for DaosClientBuilder {
fn default() -> Self {
Self {
pool_label: None,
pool_uuid: None,
pool_sys: None,
pool_flags: 0,
container_label: None,
container_uuid: None,
container_flags: 0,
object_type: ObjectType::KvHashed,
object_class: ObjectClass::UNKNOWN,
object_hints: ObjectClassHints::NONE,
}
}
}
impl DaosClientBuilder {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn pool_label(mut self, label: impl Into<String>) -> Self {
self.pool_label = Some(label.into());
self
}
#[inline]
pub fn pool_uuid(mut self, uuid: impl Into<String>) -> Self {
self.pool_uuid = Some(uuid.into());
self
}
#[inline]
pub fn pool_system(mut self, sys: impl Into<String>) -> Self {
self.pool_sys = Some(sys.into());
self
}
#[inline]
pub fn pool_flags(mut self, flags: u32) -> Self {
self.pool_flags = flags;
self
}
#[inline]
pub fn container_label(mut self, label: impl Into<String>) -> Self {
self.container_label = Some(label.into());
self
}
#[inline]
pub fn container_uuid(mut self, uuid: impl Into<String>) -> Self {
self.container_uuid = Some(uuid.into());
self
}
#[inline]
pub fn container_flags(mut self, flags: u32) -> Self {
self.container_flags = flags;
self
}
#[inline]
pub fn object_type(mut self, ty: ObjectType) -> Self {
self.object_type = ty;
self
}
#[inline]
pub fn object_class(mut self, oc: ObjectClass) -> Self {
self.object_class = oc;
self
}
#[inline]
pub fn object_hints(mut self, hints: ObjectClassHints) -> Self {
self.object_hints = hints;
self
}
fn validate(&self) -> std::result::Result<(), FacadeError> {
match (&self.pool_label, &self.pool_uuid) {
(Some(_), Some(_)) => Err(FacadeError::InvalidConfig(
"cannot specify both pool_label and pool_uuid".into(),
)),
(None, None) => Err(FacadeError::InvalidConfig(
"must specify either pool_label or pool_uuid".into(),
)),
_ => Ok(()),
}?;
match (&self.container_label, &self.container_uuid) {
(Some(_), Some(_)) => Err(FacadeError::InvalidConfig(
"cannot specify both container_label and container_uuid".into(),
)),
(None, None) => Err(FacadeError::InvalidConfig(
"must specify either container_label or container_uuid".into(),
)),
_ => Ok(()),
}?;
Ok(())
}
pub fn build(&self) -> Result<DaosClient> {
self.validate()?;
let runtime = DaosRuntime::new()?;
let pool = {
let mut builder = PoolBuilder::new();
if let Some(ref label) = self.pool_label {
builder = builder.label(label);
}
if let Some(ref uuid) = self.pool_uuid {
builder = builder.uuid(uuid);
}
if let Some(ref sys) = self.pool_sys {
builder = builder.system(sys);
}
builder.flags(self.pool_flags).build()?
};
let (container_identifier, open_by) = if let Some(label) = self.container_label.as_deref() {
(label.to_string(), ContainerOpen::ByLabel)
} else if let Some(uuid) = self.container_uuid.as_deref() {
(uuid.to_string(), ContainerOpen::ByUuid)
} else {
unreachable!("validated above");
};
let _ = pool.open_container(&container_identifier, open_by, self.container_flags)?;
Ok(DaosClient {
runtime,
pool,
container_label: container_identifier,
container_flags: self.container_flags,
default_object_type: self.object_type,
default_object_class: self.object_class,
default_object_hints: self.object_hints,
})
}
}
#[derive(Debug)]
pub struct DaosClient {
#[allow(dead_code)]
runtime: DaosRuntime,
pool: Pool,
container_label: String,
container_flags: u32,
default_object_type: ObjectType,
default_object_class: ObjectClass,
default_object_hints: ObjectClassHints,
}
impl DaosClient {
#[inline]
pub fn builder() -> DaosClientBuilder {
DaosClientBuilder::new()
}
#[inline]
pub fn pool(&self) -> &Pool {
&self.pool
}
pub fn container(&self) -> Result<Container<'_>> {
self.pool.open_container(
&self.container_label,
ContainerOpen::ByLabel,
self.container_flags,
)
}
#[inline]
pub fn default_object_type(&self) -> ObjectType {
self.default_object_type
}
#[inline]
pub fn default_object_class(&self) -> ObjectClass {
self.default_object_class
}
#[inline]
pub fn default_object_hints(&self) -> ObjectClassHints {
self.default_object_hints
}
pub fn alloc_oid(
&self,
object_type: ObjectType,
oclass: ObjectClass,
hints: ObjectClassHints,
) -> Result<ObjectId> {
let container = self.container()?;
let coh = container.as_handle()?;
let lo = container.alloc_oids(1)?;
let mut oid = ObjectId::from_parts(0, lo);
generate_oid(coh, &mut oid, object_type, oclass, hints)?;
Ok(oid)
}
#[inline]
pub fn object_builder(&self) -> ObjectBuilder<'_> {
ObjectBuilder::new(self)
}
#[inline]
pub fn open_object(&self, oid: ObjectId, mode: ObjectOpenMode) -> Result<Object> {
let container = self.container()?;
Object::open_in(&container, oid, mode)
}
pub fn put<D, A, V>(&self, oid: ObjectId, dkey: D, akey: A, value: V) -> Result<()>
where
D: AsRef<[u8]>,
A: AsRef<[u8]>,
V: AsRef<[u8]>,
{
self.put_tx(oid, Tx::none(), dkey, akey, value)
}
pub fn put_tx<D, A, V>(
&self,
oid: ObjectId,
tx: Tx<'_>,
dkey: D,
akey: A,
value: V,
) -> Result<()>
where
D: AsRef<[u8]>,
A: AsRef<[u8]>,
V: AsRef<[u8]>,
{
let container = self.container()?;
let object = Object::open_in(&container, oid, ObjectOpenMode::ReadWrite)?;
let dkey = DKey::new(dkey.as_ref().to_vec())?;
let akey = AKey::new(akey.as_ref().to_vec())?;
let value_bytes = value.as_ref();
let iod = Iod::Single(
IodSingleBuilder::new(akey)
.value_len(value_bytes.len())
.build()?,
);
let sgl = Sgl::builder()
.push(IoBuffer::from_vec(value_bytes.to_vec()))
.build()?;
object.update(&tx, &dkey, &iod, &sgl)
}
pub fn get<D, A>(&self, oid: ObjectId, dkey: D, akey: A, buffer: &mut [u8]) -> Result<()>
where
D: AsRef<[u8]>,
A: AsRef<[u8]>,
{
self.get_tx(oid, Tx::none(), dkey, akey, buffer)
}
pub fn get_tx<D, A>(
&self,
oid: ObjectId,
tx: Tx<'_>,
dkey: D,
akey: A,
buffer: &mut [u8],
) -> Result<()>
where
D: AsRef<[u8]>,
A: AsRef<[u8]>,
{
let container = self.container()?;
let object = Object::open_in(&container, oid, ObjectOpenMode::ReadWrite)?;
let dkey = DKey::new(dkey.as_ref().to_vec())?;
let akey = AKey::new(akey.as_ref().to_vec())?;
let iod = Iod::Single(
IodSingleBuilder::new(akey)
.value_len(buffer.len())
.build()?,
);
let mut sgl = Sgl::builder()
.push(IoBuffer::from_vec(buffer.to_vec()))
.build()?;
object.fetch(&tx, &dkey, &iod, &mut sgl)?;
let fetched = sgl
.buffers()
.first()
.ok_or(DaosError::Internal("empty SGL after fetch".into()))?;
if fetched.len() != buffer.len() {
return Err(DaosError::Internal("fetch buffer length mismatch".into()));
}
buffer.copy_from_slice(fetched.as_slice());
Ok(())
}
pub fn delete(
&self,
oid: ObjectId,
dkey: Option<&[u8]>,
akeys: Option<&[&[u8]]>,
tx: Tx<'_>,
) -> Result<()> {
let container = self.container()?;
let object = Object::open_in(&container, oid, ObjectOpenMode::ReadWrite)?;
match (dkey, akeys) {
(Some(dk), Some(aks)) => {
let dkey = DKey::new(dk)?;
let mut akeys_vec: Vec<AKey> = Vec::new();
for a in aks {
akeys_vec.push(AKey::new(*a)?);
}
object.punch_akeys(&tx, &dkey, &akeys_vec)
}
(Some(dk), None) => {
let dkey = DKey::new(dk)?;
object.punch_dkeys(&tx, &[dkey])
}
(None, _) => object.punch(&tx),
}
}
}
#[derive(Debug)]
pub struct ObjectBuilder<'c> {
client: &'c DaosClient,
object_type: ObjectType,
object_class: ObjectClass,
object_hints: ObjectClassHints,
}
impl<'c> ObjectBuilder<'c> {
fn new(client: &'c DaosClient) -> Self {
Self {
client,
object_type: client.default_object_type,
object_class: client.default_object_class,
object_hints: client.default_object_hints,
}
}
#[inline]
pub fn object_type(mut self, ty: ObjectType) -> Self {
self.object_type = ty;
self
}
#[inline]
pub fn object_class(mut self, oc: ObjectClass) -> Self {
self.object_class = oc;
self
}
#[inline]
pub fn object_hints(mut self, hints: ObjectClassHints) -> Self {
self.object_hints = hints;
self
}
#[inline]
pub fn alloc(&self) -> Result<ObjectId> {
self.client
.alloc_oid(self.object_type, self.object_class, self.object_hints)
}
pub fn create(self, mode: ObjectOpenMode) -> Result<Object> {
let oid = self.alloc()?;
let container = self.client.container()?;
Object::open_in(&container, oid, mode)
}
#[inline]
pub fn open(&self, oid: ObjectId, mode: ObjectOpenMode) -> Result<Object> {
let container = self.client.container()?;
Object::open_in(&container, oid, mode)
}
#[inline]
pub fn open_or_create(self, oid: ObjectId, mode: ObjectOpenMode) -> Result<Object> {
let _ = self.alloc();
self.open(oid, mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::container::flags::CONT_OPEN_RW;
use crate::pool::flags::POOL_CONNECT_NONE;
#[test]
fn test_client_builder_default() {
let builder = DaosClientBuilder::new();
assert!(builder.pool_label.is_none());
assert!(builder.pool_uuid.is_none());
assert!(builder.container_label.is_none());
assert!(builder.container_uuid.is_none());
}
#[test]
fn test_client_builder_pool_chaining() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.pool_system("daos_server")
.pool_flags(POOL_CONNECT_NONE);
assert_eq!(builder.pool_label.as_deref(), Some("mypool"));
assert_eq!(builder.pool_sys.as_deref(), Some("daos_server"));
assert_eq!(builder.pool_flags, POOL_CONNECT_NONE);
}
#[test]
fn test_client_builder_container_chaining() {
let builder = DaosClientBuilder::new()
.container_label("mycontainer")
.container_flags(CONT_OPEN_RW);
assert_eq!(builder.container_label.as_deref(), Some("mycontainer"));
assert_eq!(builder.container_flags, CONT_OPEN_RW);
}
#[test]
fn test_client_builder_object_config() {
let builder = DaosClientBuilder::new()
.object_type(ObjectType::KvHashed)
.object_class(ObjectClass::UNKNOWN)
.object_hints(ObjectClassHints::NONE);
assert_eq!(builder.object_type, ObjectType::KvHashed);
}
#[test]
fn test_client_builder_validate_pool_both_label_and_uuid() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.pool_uuid("12345678-1234-1234-1234-123456789012");
let result = builder.validate();
assert!(result.is_err());
}
#[test]
fn test_client_builder_validate_container_both_label_and_uuid() {
let builder = DaosClientBuilder::new()
.container_label("mycontainer")
.container_uuid("12345678-1234-1234-1234-123456789012");
let result = builder.validate();
assert!(result.is_err());
}
#[test]
fn test_client_builder_validate_neither_pool() {
let builder = DaosClientBuilder::new().container_label("mycontainer");
let result = builder.validate();
assert!(result.is_err());
}
#[test]
fn test_client_builder_validate_neither_container() {
let builder = DaosClientBuilder::new().pool_label("mypool");
let result = builder.validate();
assert!(result.is_err());
}
#[test]
fn test_object_builder_default_type() {
let builder = DaosClientBuilder::new();
assert_eq!(builder.object_type, ObjectType::KvHashed);
}
#[test]
fn test_facade_error_display() {
let err = FacadeError::NotConnected;
assert_eq!(format!("{}", err), "client not connected");
let err = FacadeError::InvalidConfig("test".into());
assert_eq!(format!("{}", err), "invalid configuration: test");
let err = FacadeError::Daos(DaosError::NotFound);
assert_eq!(format!("{}", err), "DAOS error: Entity not found");
}
#[test]
fn test_facade_error_from_daos_error() {
let facade_err: FacadeError = DaosError::NotFound.into();
assert!(matches!(facade_err, FacadeError::Daos(DaosError::NotFound)));
}
#[test]
fn test_daos_error_from_facade_error() {
let daos_err: DaosError = FacadeError::Daos(DaosError::Busy).into();
assert!(matches!(daos_err, DaosError::Busy));
let daos_err: DaosError = FacadeError::NotConnected.into();
assert!(matches!(daos_err, DaosError::InvalidArg));
}
#[test]
fn test_object_builder_debug() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.container_label("mycontainer");
let debug_str = format!("{:?}", builder);
assert!(debug_str.contains("DaosClientBuilder"));
}
#[test]
fn test_daos_client_builder_all_options() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.pool_system("daos_server")
.pool_flags(1)
.container_label("mycontainer")
.container_flags(2)
.object_type(ObjectType::Array)
.object_class(ObjectClass::UNKNOWN)
.object_hints(ObjectClassHints::NONE);
assert_eq!(builder.pool_label.as_deref(), Some("mypool"));
assert_eq!(builder.pool_sys.as_deref(), Some("daos_server"));
assert_eq!(builder.pool_flags, 1);
assert_eq!(builder.container_label.as_deref(), Some("mycontainer"));
assert_eq!(builder.container_flags, 2);
assert_eq!(builder.object_type, ObjectType::Array);
}
#[test]
fn test_daos_client_builder_uuid_based() {
let builder = DaosClientBuilder::new()
.pool_uuid("12345678-1234-1234-1234-123456789012")
.container_uuid("87654321-4321-4321-4321-210987654321");
assert!(builder.pool_label.is_none());
assert!(builder.pool_uuid.is_some());
assert!(builder.container_label.is_none());
assert!(builder.container_uuid.is_some());
}
#[test]
fn test_object_builder_chaining() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.container_label("mycontainer")
.object_type(ObjectType::MultiHashed)
.object_class(ObjectClass::UNKNOWN)
.object_hints(ObjectClassHints::RDD_RP);
assert_eq!(builder.object_type, ObjectType::MultiHashed);
assert_eq!(builder.object_class, ObjectClass::UNKNOWN);
assert!(builder.object_hints.as_raw() & ObjectClassHints::RDD_RP.as_raw() != 0);
}
#[test]
fn test_facade_error_not_connected_display() {
let err = FacadeError::NotConnected;
assert!(format!("{:?}", err).contains("NotConnected"));
}
#[test]
fn test_facade_error_invalid_config_display() {
let err = FacadeError::InvalidConfig("missing pool".into());
assert!(format!("{}", err).contains("missing pool"));
}
#[test]
fn test_daos_client_debug() {
let builder = DaosClientBuilder::new()
.pool_label("mypool")
.container_label("mycontainer");
let debug_str = format!("{:?}", builder);
assert!(!debug_str.is_empty());
}
}