use std::sync::Arc;
use anyhow::Result;
use futures::future::BoxFuture;
use crate::{BlockId, SequenceHash};
use kvbm_common::LogicalLayoutHandle;
use kvbm_physical::layout::LayoutConfig;
use kvbm_physical::transfer::PhysicalLayout;
#[cfg(feature = "s3")]
pub mod s3;
pub trait KeyFormatter: Send + Sync {
fn format_key(&self, hash: &SequenceHash) -> String;
}
#[derive(Debug, Clone, Default)]
pub struct DefaultKeyFormatter;
impl KeyFormatter for DefaultKeyFormatter {
fn format_key(&self, hash: &SequenceHash) -> String {
hash.to_string()
}
}
#[derive(Debug, Clone)]
pub struct RankPrefixedKeyFormatter {
rank: usize,
}
impl RankPrefixedKeyFormatter {
pub fn new(rank: usize) -> Self {
Self { rank }
}
pub fn rank(&self) -> usize {
self.rank
}
}
impl KeyFormatter for RankPrefixedKeyFormatter {
fn format_key(&self, hash: &SequenceHash) -> String {
format!("{}/{}", self.rank, hash)
}
}
pub fn create_key_formatter(rank: Option<usize>) -> Arc<dyn KeyFormatter> {
match rank {
Some(r) => Arc::new(RankPrefixedKeyFormatter::new(r)),
None => Arc::new(DefaultKeyFormatter),
}
}
pub trait LayoutConfigExt {
fn block_size_bytes(&self) -> usize;
fn region_size(&self) -> usize;
}
impl LayoutConfigExt for LayoutConfig {
fn block_size_bytes(&self) -> usize {
self.num_layers
.saturating_mul(self.outer_dim)
.saturating_mul(self.page_size)
.saturating_mul(self.inner_dim)
.saturating_mul(self.dtype_width_bytes)
}
fn region_size(&self) -> usize {
self.page_size
.saturating_mul(self.inner_dim)
.saturating_mul(self.dtype_width_bytes)
}
}
pub trait ObjectClient: Send + Sync {
fn has_object(&self, key: &[u8]) -> anyhow::Result<bool>;
fn put_object(&self, key: &[u8], data: &[&[u8]]) -> anyhow::Result<()>;
fn get_object(&self, key: &[u8], data: &mut [&mut [u8]]) -> anyhow::Result<()>;
}
pub trait ObjectBlockOps: Send + Sync {
fn has_blocks(
&self,
keys: Vec<SequenceHash>,
) -> BoxFuture<'static, Vec<(SequenceHash, Option<usize>)>>;
fn put_blocks(
&self,
keys: Vec<SequenceHash>,
src_layout: LogicalLayoutHandle,
block_ids: Vec<BlockId>,
) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>>;
fn get_blocks(
&self,
keys: Vec<SequenceHash>,
dst_layout: LogicalLayoutHandle,
block_ids: Vec<BlockId>,
) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>>;
fn put_blocks_with_layout(
&self,
keys: Vec<SequenceHash>,
_layout: PhysicalLayout,
_block_ids: Vec<BlockId>,
) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>> {
Box::pin(async move { keys.into_iter().map(Err).collect() })
}
fn get_blocks_with_layout(
&self,
keys: Vec<SequenceHash>,
_layout: PhysicalLayout,
_block_ids: Vec<BlockId>,
) -> BoxFuture<'static, Vec<Result<SequenceHash, SequenceHash>>> {
Box::pin(async move { keys.into_iter().map(Err).collect() })
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LockFileContent {
pub instance_id: String,
pub acquired_at: String,
pub deadline: String,
}
pub trait ObjectLockManager: Send + Sync {
fn has_meta(&self, hash: SequenceHash) -> BoxFuture<'static, Result<bool>>;
fn try_acquire_lock(&self, hash: SequenceHash) -> BoxFuture<'static, Result<bool>>;
fn create_meta(&self, hash: SequenceHash) -> BoxFuture<'static, Result<()>>;
fn release_lock(&self, hash: SequenceHash) -> BoxFuture<'static, Result<()>>;
}
#[cfg(feature = "s3")]
pub async fn create_object_client(
config: &kvbm_config::ObjectConfig,
rank: Option<usize>,
) -> Result<Arc<dyn ObjectBlockOps>> {
use kvbm_config::ObjectClientConfig;
use s3::{S3Config, S3ObjectBlockClient};
let key_formatter = create_key_formatter(rank);
match &config.client {
ObjectClientConfig::S3(s3_config) => {
let config = S3Config::from_object_config(s3_config);
let client = S3ObjectBlockClient::with_key_formatter(config, key_formatter).await?;
Ok(Arc::new(client))
}
ObjectClientConfig::Nixl(_nixl_config) => {
anyhow::bail!("Nixl object storage backend not yet implemented")
}
}
}
#[cfg(not(feature = "s3"))]
pub async fn create_object_client(
_config: &kvbm_config::ObjectConfig,
_rank: Option<usize>,
) -> Result<Arc<dyn ObjectBlockOps>> {
anyhow::bail!("Object storage requires the 's3' feature to be enabled")
}
#[cfg(feature = "s3")]
pub async fn create_lock_manager(
config: &kvbm_config::ObjectConfig,
instance_id: String,
) -> Result<Arc<dyn ObjectLockManager>> {
use kvbm_config::ObjectClientConfig;
use s3::{S3Config, S3LockManager, S3ObjectBlockClient};
match &config.client {
ObjectClientConfig::S3(s3_config) => {
let config = S3Config::from_object_config(s3_config);
let client = Arc::new(S3ObjectBlockClient::new(config).await?);
let manager = S3LockManager::new(client, instance_id);
Ok(Arc::new(manager))
}
ObjectClientConfig::Nixl(_nixl_config) => {
anyhow::bail!("Nixl object storage backend not yet implemented")
}
}
}
#[cfg(not(feature = "s3"))]
pub async fn create_lock_manager(
_config: &kvbm_config::ObjectConfig,
_instance_id: String,
) -> Result<Arc<dyn ObjectLockManager>> {
anyhow::bail!("Object storage requires the 's3' feature to be enabled")
}