// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Used by test_distributed_lock_4_nodes_grpc in lock.rs #![allow(dead_code)] use bytes::Bytes; use futures::Stream; use rustfs_lock::{LockClient, LockRequest}; use rustfs_protos::{ models::PingBodyBuilder, proto_gen::node_service::{ BatchGenerallyLockRequest, BatchGenerallyLockResponse, BatchReadVersionRequest, BatchReadVersionResponse, GenerallyLockRequest, GenerallyLockResponse, GenerallyLockResult, PingRequest, PingResponse, SnapshotLeaseMutationResponse, SnapshotLeaseReleaseRequest, SnapshotLeaseRenewRequest, SnapshotLeaseRequest, SnapshotLeaseResponse, node_service_server::NodeService, }, }; use std::pin::Pin; use std::sync::Arc; use tokio::net::TcpListener; use tokio_stream::wrappers::TcpListenerStream; use tonic::{Request, Response, Status}; use tracing::debug; type ResponseStream = Pin> + Send>>; fn lock_result_from_response(response: rustfs_lock::LockResponse) -> GenerallyLockResult { GenerallyLockResult { success: response.success, error_info: response.error, lock_info: response.lock_info.and_then(|info| serde_json::to_string(&info).ok()), } } fn lock_result_from_error(error: impl Into) -> GenerallyLockResult { GenerallyLockResult { success: false, error_info: Some(error.into()), lock_info: None, } } fn lock_result_from_release(lock_id: &rustfs_lock::LockId, success: bool) -> GenerallyLockResult { if success { GenerallyLockResult { success: true, error_info: None, lock_info: None, } } else { lock_result_from_error(format!("lock not found for release: {lock_id}")) } } /// Minimal NodeService implementation that only supports Lock RPCs /// Used for testing distributed lock scenarios with real gRPC #[derive(Debug)] pub struct MinimalLockNodeService { lock_client: Arc, } impl MinimalLockNodeService { pub fn new(lock_client: Arc) -> Self { Self { lock_client } } } #[tonic::async_trait] impl NodeService for MinimalLockNodeService { async fn ping(&self, _request: Request) -> Result, Status> { debug!("MinimalLockNodeService: PING"); let mut fbb = flatbuffers::FlatBufferBuilder::new(); let payload = fbb.create_vector(b"pong"); let mut builder = PingBodyBuilder::new(&mut fbb); builder.add_payload(payload); let root = builder.finish(); fbb.finish(root, None); let finished_data = fbb.finished_data(); Ok(Response::new(PingResponse { version: 1, body: Bytes::copy_from_slice(finished_data), })) } async fn batch_read_version( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("MinimalLockNodeService only supports lock RPCs")) } async fn acquire_snapshot_lease( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("MinimalLockNodeService only supports lock RPCs")) } async fn renew_snapshot_lease( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("MinimalLockNodeService only supports lock RPCs")) } async fn release_snapshot_lease( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("MinimalLockNodeService only supports lock RPCs")) } async fn lock(&self, request: Request) -> Result, Status> { let request = request.into_inner(); let args: LockRequest = match serde_json::from_str(&request.args) { Ok(args) => args, Err(err) => { return Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!("can not decode args, err: {err}")), lock_info: None, })); } }; match self.lock_client.acquire_lock(&args).await { Ok(result) => { let lock_info_json = result.lock_info.as_ref().and_then(|info| serde_json::to_string(info).ok()); Ok(Response::new(GenerallyLockResponse { success: result.success, error_info: result.error, lock_info: lock_info_json, })) } Err(err) => Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!( "can not lock, resource: {0}, owner: {1}, err: {2}", args.resource, args.owner, err )), lock_info: None, })), } } async fn un_lock(&self, request: Request) -> Result, Status> { let request = request.into_inner(); let args: LockRequest = match serde_json::from_str(&request.args) { Ok(args) => args, Err(err) => { return Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!("can not decode args, err: {err}")), lock_info: None, })); } }; match self.lock_client.release(&args.lock_id).await { Ok(success) => { let result = lock_result_from_release(&args.lock_id, success); Ok(Response::new(GenerallyLockResponse { success: result.success, error_info: result.error_info, lock_info: None, })) } Err(err) => Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!( "can not unlock, resource: {0}, owner: {1}, err: {2}", args.resource, args.owner, err )), lock_info: None, })), } } async fn force_un_lock(&self, request: Request) -> Result, Status> { let request = request.into_inner(); let args: LockRequest = match serde_json::from_str(&request.args) { Ok(args) => args, Err(err) => { return Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!("can not decode args, err: {err}")), lock_info: None, })); } }; match self.lock_client.force_release(&args.lock_id).await { Ok(success) => { let result = lock_result_from_release(&args.lock_id, success); Ok(Response::new(GenerallyLockResponse { success: result.success, error_info: result.error_info, lock_info: None, })) } Err(err) => Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!( "can not force_unlock, resource: {0}, owner: {1}, err: {2}", args.resource, args.owner, err )), lock_info: None, })), } } async fn refresh(&self, request: Request) -> Result, Status> { let request = request.into_inner(); let args: LockRequest = match serde_json::from_str(&request.args) { Ok(args) => args, Err(err) => { return Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!("can not decode args, err: {err}")), lock_info: None, })); } }; match self.lock_client.refresh(&args.lock_id).await { Ok(success) => Ok(Response::new(GenerallyLockResponse { success, error_info: None, lock_info: None, })), Err(err) => Ok(Response::new(GenerallyLockResponse { success: false, error_info: Some(format!("can not refresh, err: {err}")), lock_info: None, })), } } async fn lock_batch( &self, request: Request, ) -> Result, Status> { let request = request.into_inner(); let mut results = vec![lock_result_from_error("request was not processed"); request.args.len()]; let mut valid_requests = Vec::with_capacity(request.args.len()); let mut valid_indices = Vec::with_capacity(request.args.len()); for (idx, arg) in request.args.iter().enumerate() { match serde_json::from_str::(arg) { Ok(args) => { valid_requests.push(args); valid_indices.push(idx); } Err(err) => { results[idx] = lock_result_from_error(format!("can not decode args, err: {err}")); } } } if !valid_requests.is_empty() { match self.lock_client.acquire_locks_batch(&valid_requests).await { Ok(batch_results) => { for (result_idx, response) in batch_results.into_iter().enumerate() { if let Some(request_idx) = valid_indices.get(result_idx) { results[*request_idx] = lock_result_from_response(response); } } } Err(err) => { for request_idx in valid_indices { results[request_idx] = lock_result_from_error(format!("can not batch lock, err: {err}")); } } } } Ok(Response::new(BatchGenerallyLockResponse { results })) } async fn un_lock_batch( &self, request: Request, ) -> Result, Status> { let request = request.into_inner(); let mut results = vec![lock_result_from_error("request was not processed"); request.args.len()]; let mut lock_ids = Vec::with_capacity(request.args.len()); let mut valid_indices = Vec::with_capacity(request.args.len()); for (idx, arg) in request.args.iter().enumerate() { match serde_json::from_str::(arg) { Ok(args) => { lock_ids.push(args.lock_id); valid_indices.push(idx); } Err(err) => { results[idx] = lock_result_from_error(format!("can not decode args, err: {err}")); } } } if !lock_ids.is_empty() { match self.lock_client.release_locks_batch(&lock_ids).await { Ok(batch_results) => { for (result_idx, success) in batch_results.into_iter().enumerate() { if let Some(request_idx) = valid_indices.get(result_idx) { results[*request_idx] = match lock_ids.get(result_idx) { Some(lock_id) => lock_result_from_release(lock_id, success), None => lock_result_from_error(format!("unlock response index out of range: {result_idx}")), }; } } } Err(err) => { for request_idx in valid_indices { results[request_idx] = lock_result_from_error(format!("can not batch unlock, err: {err}")); } } } } Ok(Response::new(BatchGenerallyLockResponse { results })) } // All other methods return unimplemented async fn heal_bucket( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn list_bucket( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn make_bucket( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_bucket_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_bucket( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_all( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn write_all( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn verify_file( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_parts( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn check_parts( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn rename_part( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn prepare_part_transaction( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn settle_part_transaction( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn rename_file( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn write( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } type WriteStreamStream = ResponseStream; async fn write_stream( &self, _request: Request>, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } type ReadAtStream = ResponseStream; async fn read_at( &self, _request: Request>, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn list_dir( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } type WalkDirStream = ResponseStream; async fn walk_dir( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn rename_data( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn make_volumes( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn make_volume( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn list_volumes( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn stat_volume( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_paths( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn update_metadata( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_metadata( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn write_metadata( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_version( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_xl( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_version( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_versions( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn read_multiple( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_volume( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn disk_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn local_storage_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn server_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_cpus( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_net_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_partitions( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_os_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_se_linux_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_sys_config( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_sys_errors( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_mem_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_proc_info( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_bucket_metadata( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_policy( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_group( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_policy_mapping( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_rebalance_meta( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn start_decommission( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn cancel_decommission( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn clear_decommission( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_metrics( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_live_events( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn start_profiling( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn download_profile_data( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_bucket_stats( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_sr_metrics( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_all_bucket_stats( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_bucket_metadata( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_policy( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_user( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn delete_service_account( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_user( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_service_account( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn reload_site_replication_config( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn signal_service( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn scanner_activity( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn background_heal_status( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn get_metacache_listing( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn update_metacache_listing( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn reload_pool_meta( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn stop_rebalance( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } async fn load_transition_tier_config( &self, _request: Request, ) -> Result, Status> { Err(Status::unimplemented("lock-only test server")) } } /// Spawn a gRPC lock server on a random port /// Returns the address and a shutdown handle pub async fn spawn_lock_server( lock_client: Arc, ) -> std::result::Result<(String, tokio::task::JoinHandle<()>), Box> { let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; let addr_str = format!("http://127.0.0.1:{}", addr.port()); let service = MinimalLockNodeService::new(lock_client); let server = tonic::transport::Server::builder() .add_service(rustfs_protos::proto_gen::node_service::node_service_server::NodeServiceServer::new( service, )) .serve_with_incoming(TcpListenerStream::new(listener)); let handle = tokio::spawn(async move { if let Err(e) = server.await { eprintln!("gRPC server error: {}", e); } }); Ok((addr_str, handle)) }