feat(cluster): add clustered storage backend with QUIC transport, erasure coding, and shard management
This commit is contained in:
@@ -10,6 +10,7 @@ use tokio::fs;
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufWriter};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::cluster::coordinator::DistributedStore;
|
||||
use crate::error::StorageError;
|
||||
|
||||
// ============================
|
||||
@@ -795,6 +796,196 @@ impl FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// StorageBackend enum
|
||||
// ============================
|
||||
|
||||
/// Unified storage backend that dispatches to either standalone (FileStore)
|
||||
/// or clustered (DistributedStore) storage.
|
||||
pub enum StorageBackend {
|
||||
Standalone(FileStore),
|
||||
Clustered(DistributedStore),
|
||||
}
|
||||
|
||||
impl StorageBackend {
|
||||
pub fn policies_dir(&self) -> std::path::PathBuf {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.policies_dir(),
|
||||
StorageBackend::Clustered(_) => PathBuf::from(".policies"), // TODO: proper policies in cluster mode
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize(&self) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.initialize().await,
|
||||
StorageBackend::Clustered(_) => Ok(()), // Cluster init happens separately
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reset(&self) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.reset().await,
|
||||
StorageBackend::Clustered(_) => Ok(()), // TODO: cluster reset
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_buckets(&self) -> Result<Vec<BucketInfo>> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.list_buckets().await,
|
||||
StorageBackend::Clustered(ds) => ds.list_buckets().await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bucket_exists(&self, bucket: &str) -> bool {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.bucket_exists(bucket).await,
|
||||
StorageBackend::Clustered(ds) => ds.bucket_exists(bucket).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_bucket(&self, bucket: &str) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.create_bucket(bucket).await,
|
||||
StorageBackend::Clustered(ds) => ds.create_bucket(bucket).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_bucket(&self, bucket: &str) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.delete_bucket(bucket).await,
|
||||
StorageBackend::Clustered(ds) => ds.delete_bucket(bucket).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn put_object(
|
||||
&self,
|
||||
bucket: &str,
|
||||
key: &str,
|
||||
body: Incoming,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<PutResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.put_object(bucket, key, body, metadata).await,
|
||||
StorageBackend::Clustered(ds) => ds.put_object(bucket, key, body, metadata).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_object(
|
||||
&self,
|
||||
bucket: &str,
|
||||
key: &str,
|
||||
range: Option<(u64, u64)>,
|
||||
) -> Result<GetResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.get_object(bucket, key, range).await,
|
||||
StorageBackend::Clustered(ds) => ds.get_object(bucket, key, range).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn head_object(&self, bucket: &str, key: &str) -> Result<HeadResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.head_object(bucket, key).await,
|
||||
StorageBackend::Clustered(ds) => ds.head_object(bucket, key).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_object(&self, bucket: &str, key: &str) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.delete_object(bucket, key).await,
|
||||
StorageBackend::Clustered(ds) => ds.delete_object(bucket, key).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn copy_object(
|
||||
&self,
|
||||
src_bucket: &str,
|
||||
src_key: &str,
|
||||
dest_bucket: &str,
|
||||
dest_key: &str,
|
||||
metadata_directive: &str,
|
||||
new_metadata: Option<HashMap<String, String>>,
|
||||
) -> Result<CopyResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => {
|
||||
fs.copy_object(src_bucket, src_key, dest_bucket, dest_key, metadata_directive, new_metadata).await
|
||||
}
|
||||
StorageBackend::Clustered(ds) => {
|
||||
ds.copy_object(src_bucket, src_key, dest_bucket, dest_key, metadata_directive, new_metadata).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_objects(
|
||||
&self,
|
||||
bucket: &str,
|
||||
prefix: &str,
|
||||
delimiter: &str,
|
||||
max_keys: usize,
|
||||
continuation_token: Option<&str>,
|
||||
) -> Result<ListObjectsResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => {
|
||||
fs.list_objects(bucket, prefix, delimiter, max_keys, continuation_token).await
|
||||
}
|
||||
StorageBackend::Clustered(ds) => {
|
||||
ds.list_objects(bucket, prefix, delimiter, max_keys, continuation_token).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initiate_multipart(
|
||||
&self,
|
||||
bucket: &str,
|
||||
key: &str,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<String> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.initiate_multipart(bucket, key, metadata).await,
|
||||
StorageBackend::Clustered(ds) => ds.initiate_multipart(bucket, key, metadata).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upload_part(
|
||||
&self,
|
||||
upload_id: &str,
|
||||
part_number: u32,
|
||||
body: Incoming,
|
||||
) -> Result<(String, u64)> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.upload_part(upload_id, part_number, body).await,
|
||||
StorageBackend::Clustered(ds) => ds.upload_part(upload_id, part_number, body).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn complete_multipart(
|
||||
&self,
|
||||
upload_id: &str,
|
||||
parts: &[(u32, String)],
|
||||
) -> Result<CompleteMultipartResult> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.complete_multipart(upload_id, parts).await,
|
||||
StorageBackend::Clustered(ds) => ds.complete_multipart(upload_id, parts).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn abort_multipart(&self, upload_id: &str) -> Result<()> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.abort_multipart(upload_id).await,
|
||||
StorageBackend::Clustered(ds) => ds.abort_multipart(upload_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_multipart_uploads(
|
||||
&self,
|
||||
bucket: &str,
|
||||
) -> Result<Vec<MultipartUploadInfo>> {
|
||||
match self {
|
||||
StorageBackend::Standalone(fs) => fs.list_multipart_uploads(bucket).await,
|
||||
StorageBackend::Clustered(ds) => ds.list_multipart_uploads(bucket).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Key encoding (identity on Linux)
|
||||
// ============================
|
||||
|
||||
Reference in New Issue
Block a user