mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 02:33:15 +00:00
fix(multipart): preserve retried parts on quorum failure (#5363)
* fix(multipart): preserve retried parts on quorum failure * style(multipart): format transaction rollback * fix(proto): regenerate multipart transaction RPCs * fix(multipart): import rollback marker constant * fix(multipart): export transaction action * fix: import multipart transaction test requests
This commit is contained in:
@@ -926,10 +926,24 @@ impl Node for NodeService {
|
||||
self.handle_check_parts(request).await
|
||||
}
|
||||
|
||||
async fn prepare_part_transaction(
|
||||
&self,
|
||||
request: Request<PreparePartTransactionRequest>,
|
||||
) -> Result<Response<PreparePartTransactionResponse>, Status> {
|
||||
self.handle_prepare_part_transaction(request).await
|
||||
}
|
||||
|
||||
async fn rename_part(&self, request: Request<RenamePartRequest>) -> Result<Response<RenamePartResponse>, Status> {
|
||||
self.handle_rename_part(request).await
|
||||
}
|
||||
|
||||
async fn settle_part_transaction(
|
||||
&self,
|
||||
request: Request<SettlePartTransactionRequest>,
|
||||
) -> Result<Response<SettlePartTransactionResponse>, Status> {
|
||||
self.handle_settle_part_transaction(request).await
|
||||
}
|
||||
|
||||
async fn rename_file(&self, request: Request<RenameFileRequest>) -> Result<Response<RenameFileResponse>, Status> {
|
||||
self.handle_rename_file(request).await
|
||||
}
|
||||
@@ -2046,12 +2060,12 @@ mod tests {
|
||||
HealBucketRequest, HealControlRequest, ListBucketRequest, ListDirRequest, ListVolumesRequest, LoadBucketMetadataRequest,
|
||||
LoadGroupRequest, LoadPolicyMappingRequest, LoadPolicyRequest, LoadRebalanceMetaRequest, LoadServiceAccountRequest,
|
||||
LoadTransitionTierConfigRequest, LoadUserRequest, LocalStorageInfoRequest, MakeBucketRequest, MakeVolumeRequest,
|
||||
MakeVolumesRequest, Mss, PingRequest, ReadAllRequest, ReadAtRequest, ReadMultipleRequest, ReadVersionRequest,
|
||||
ReadXlRequest, ReloadPoolMetaRequest, ReloadSiteReplicationConfigRequest, RenameDataRequest, RenameFileRequest,
|
||||
RenamePartRequest, ScannerActivityRequest, ServerInfoRequest, SignalServiceRequest, StartProfilingRequest,
|
||||
StatVolumeRequest, StopRebalanceRequest, TierMutationPeerState, TierMutationPrepareRequest,
|
||||
UpdateMetacacheListingRequest, UpdateMetadataRequest, VerifyFileRequest, WriteAllRequest, WriteMetadataRequest,
|
||||
WriteRequest,
|
||||
MakeVolumesRequest, Mss, PingRequest, PreparePartTransactionRequest, ReadAllRequest, ReadAtRequest, ReadMultipleRequest,
|
||||
ReadVersionRequest, ReadXlRequest, ReloadPoolMetaRequest, ReloadSiteReplicationConfigRequest, RenameDataRequest,
|
||||
RenameFileRequest, RenamePartRequest, ScannerActivityRequest, ServerInfoRequest, SettlePartTransactionRequest,
|
||||
SignalServiceRequest, StartProfilingRequest, StatVolumeRequest, StopRebalanceRequest, TierMutationPeerState,
|
||||
TierMutationPrepareRequest, UpdateMetacacheListingRequest, UpdateMetadataRequest, VerifyFileRequest, WriteAllRequest,
|
||||
WriteMetadataRequest, WriteRequest,
|
||||
heal_control_service_client::HealControlServiceClient,
|
||||
heal_control_service_server::{HealControlService as _, HealControlServiceServer},
|
||||
node_service_client::NodeServiceClient,
|
||||
@@ -2598,6 +2612,28 @@ mod tests {
|
||||
},
|
||||
rustfs_protos::canonical_rename_part_request_body
|
||||
);
|
||||
assert_gated!(
|
||||
prepare_part_transaction,
|
||||
PreparePartTransactionRequest {
|
||||
disk: disk.clone(),
|
||||
src_volume: "src".into(),
|
||||
src_path: "sp".into(),
|
||||
dst_volume: "dst".into(),
|
||||
dst_path: "dp".into(),
|
||||
meta: vec![0x04].into(),
|
||||
},
|
||||
rustfs_protos::canonical_prepare_part_transaction_request_body
|
||||
);
|
||||
assert_gated!(
|
||||
settle_part_transaction,
|
||||
SettlePartTransactionRequest {
|
||||
disk: disk.clone(),
|
||||
volume: "dst".into(),
|
||||
path: "dp".into(),
|
||||
rollback: true,
|
||||
},
|
||||
rustfs_protos::canonical_settle_part_transaction_request_body
|
||||
);
|
||||
assert_gated!(
|
||||
delete_volume,
|
||||
DeleteVolumeRequest {
|
||||
@@ -3316,6 +3352,39 @@ mod tests {
|
||||
assert!(rename_response.error.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_part_transaction_invalid_disk() {
|
||||
let service = create_test_node_service();
|
||||
|
||||
let prepare = service
|
||||
.prepare_part_transaction(Request::new(PreparePartTransactionRequest {
|
||||
disk: "invalid-disk-path".to_string(),
|
||||
src_volume: "src-volume".to_string(),
|
||||
src_path: "src-path".to_string(),
|
||||
dst_volume: "dst-volume".to_string(),
|
||||
dst_path: "dst-path".to_string(),
|
||||
meta: Bytes::new(),
|
||||
}))
|
||||
.await
|
||||
.expect("prepare RPC should return a structured disk error")
|
||||
.into_inner();
|
||||
assert!(!prepare.success);
|
||||
assert!(prepare.error.is_some());
|
||||
|
||||
let settle = service
|
||||
.settle_part_transaction(Request::new(SettlePartTransactionRequest {
|
||||
disk: "invalid-disk-path".to_string(),
|
||||
volume: "dst-volume".to_string(),
|
||||
path: "dst-path".to_string(),
|
||||
rollback: true,
|
||||
}))
|
||||
.await
|
||||
.expect("settle RPC should return a structured disk error")
|
||||
.into_inner();
|
||||
assert!(!settle.success);
|
||||
assert!(settle.error.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_rename_file_invalid_disk() {
|
||||
let service = create_test_node_service();
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::storage::storage_api::rpc_consumer::node_service::{
|
||||
ReadMultipleResp, ReadOptions, StorageDiskRpcExt as _, UpdateMetadataOpts, validate_batch_read_version_item_count,
|
||||
};
|
||||
use crate::storage::storage_api::runtime_sources_consumer::runtime_sources;
|
||||
use crate::storage::storage_api::verify_tonic_mutation_body_digest;
|
||||
use crate::storage::storage_api::{PartTransactionAction, verify_tonic_mutation_body_digest};
|
||||
use bytes::Bytes;
|
||||
use rustfs_filemeta::FileInfo;
|
||||
use rustfs_io_metrics::internode_metrics::{
|
||||
@@ -1038,6 +1038,78 @@ impl NodeService {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_prepare_part_transaction(
|
||||
&self,
|
||||
request: Request<PreparePartTransactionRequest>,
|
||||
) -> Result<Response<PreparePartTransactionResponse>, Status> {
|
||||
verify_disk_mutation_digest(
|
||||
&request,
|
||||
rustfs_protos::canonical_prepare_part_transaction_request_body(request.get_ref()),
|
||||
"prepare_part_transaction",
|
||||
)?;
|
||||
let request = request.into_inner();
|
||||
if let Some(disk) = self.find_disk(&request.disk).await {
|
||||
match disk
|
||||
.prepare_part_transaction(
|
||||
&request.src_volume,
|
||||
&request.src_path,
|
||||
&request.dst_volume,
|
||||
&request.dst_path,
|
||||
request.meta,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(Response::new(PreparePartTransactionResponse {
|
||||
success: true,
|
||||
error: None,
|
||||
})),
|
||||
Err(err) => Ok(Response::new(PreparePartTransactionResponse {
|
||||
success: false,
|
||||
error: Some(err.into()),
|
||||
})),
|
||||
}
|
||||
} else {
|
||||
Ok(Response::new(PreparePartTransactionResponse {
|
||||
success: false,
|
||||
error: Some(DiskError::other("cannot find disk".to_string()).into()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_settle_part_transaction(
|
||||
&self,
|
||||
request: Request<SettlePartTransactionRequest>,
|
||||
) -> Result<Response<SettlePartTransactionResponse>, Status> {
|
||||
verify_disk_mutation_digest(
|
||||
&request,
|
||||
rustfs_protos::canonical_settle_part_transaction_request_body(request.get_ref()),
|
||||
"settle_part_transaction",
|
||||
)?;
|
||||
let request = request.into_inner();
|
||||
if let Some(disk) = self.find_disk(&request.disk).await {
|
||||
let action = if request.rollback {
|
||||
PartTransactionAction::Rollback
|
||||
} else {
|
||||
PartTransactionAction::Commit
|
||||
};
|
||||
match disk.settle_part_transaction(&request.volume, &request.path, action).await {
|
||||
Ok(()) => Ok(Response::new(SettlePartTransactionResponse {
|
||||
success: true,
|
||||
error: None,
|
||||
})),
|
||||
Err(err) => Ok(Response::new(SettlePartTransactionResponse {
|
||||
success: false,
|
||||
error: Some(err.into()),
|
||||
})),
|
||||
}
|
||||
} else {
|
||||
Ok(Response::new(SettlePartTransactionResponse {
|
||||
success: false,
|
||||
error: Some(DiskError::other("cannot find disk".to_string()).into()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_check_parts(
|
||||
&self,
|
||||
request: Request<CheckPartsRequest>,
|
||||
|
||||
@@ -430,9 +430,9 @@ pub(crate) mod ecstore_data_usage {
|
||||
pub(crate) mod ecstore_disk {
|
||||
pub(crate) use rustfs_ecstore::api::disk::{
|
||||
BatchReadVersionReq, BatchReadVersionResp, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskStore,
|
||||
FileInfoVersions, FileReader, FileWriter, OldCurrentSize, RUSTFS_META_BUCKET, ReadMultipleReq, ReadMultipleResp,
|
||||
ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions, get_object_disk_read_timeout,
|
||||
validate_batch_read_version_item_count,
|
||||
FileInfoVersions, FileReader, FileWriter, OldCurrentSize, PartTransactionAction, RUSTFS_META_BUCKET, ReadMultipleReq,
|
||||
ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions,
|
||||
get_object_disk_read_timeout, validate_batch_read_version_item_count,
|
||||
};
|
||||
pub(crate) use rustfs_ecstore::api::disk::{endpoint, error, error_reduce};
|
||||
}
|
||||
@@ -629,6 +629,7 @@ pub(crate) type ReadMultipleReq = ecstore_disk::ReadMultipleReq;
|
||||
pub(crate) type ReadMultipleResp = ecstore_disk::ReadMultipleResp;
|
||||
pub(crate) type ReadOptions = ecstore_disk::ReadOptions;
|
||||
pub(crate) type OldCurrentSize = ecstore_disk::OldCurrentSize;
|
||||
pub(crate) type PartTransactionAction = ecstore_disk::PartTransactionAction;
|
||||
pub(crate) type RenameDataResp = ecstore_disk::RenameDataResp;
|
||||
pub(crate) type ReplicationStatusType = ecstore_bucket::replication::ReplicationStatusType;
|
||||
pub(crate) type ReplicationStats = StorageReplicationStatsHandle;
|
||||
@@ -1097,6 +1098,15 @@ pub(crate) trait StorageDiskRpcExt {
|
||||
dst_path: &str,
|
||||
meta: bytes::Bytes,
|
||||
) -> DiskResult<()>;
|
||||
async fn prepare_part_transaction(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
meta: bytes::Bytes,
|
||||
) -> DiskResult<()>;
|
||||
async fn settle_part_transaction(&self, volume: &str, path: &str, action: PartTransactionAction) -> DiskResult<()>;
|
||||
async fn delete(&self, volume: &str, path: &str, options: DeleteOptions) -> DiskResult<()>;
|
||||
async fn verify_file(&self, volume: &str, path: &str, file_info: &rustfs_filemeta::FileInfo) -> DiskResult<CheckPartsResp>;
|
||||
async fn check_parts(&self, volume: &str, path: &str, file_info: &rustfs_filemeta::FileInfo) -> DiskResult<CheckPartsResp>;
|
||||
@@ -1241,6 +1251,21 @@ where
|
||||
ecstore_disk::DiskAPI::rename_part(self, src_volume, src_path, dst_volume, dst_path, meta).await
|
||||
}
|
||||
|
||||
async fn prepare_part_transaction(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
meta: bytes::Bytes,
|
||||
) -> DiskResult<()> {
|
||||
ecstore_disk::DiskAPI::prepare_part_transaction(self, src_volume, src_path, dst_volume, dst_path, meta).await
|
||||
}
|
||||
|
||||
async fn settle_part_transaction(&self, volume: &str, path: &str, action: PartTransactionAction) -> DiskResult<()> {
|
||||
ecstore_disk::DiskAPI::settle_part_transaction(self, volume, path, action).await
|
||||
}
|
||||
|
||||
async fn delete(&self, volume: &str, path: &str, options: DeleteOptions) -> DiskResult<()> {
|
||||
ecstore_disk::DiskAPI::delete(self, volume, path, options).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user