use std::sync::Arc; use std::time::{Duration, Instant}; use api::grpc::qdrant::collections_internal_server::CollectionsInternal; use api::grpc::qdrant::{ CollectionOperationResponse, GetCollectionInfoRequestInternal, GetCollectionInfoResponse, GetShardRecoveryPointRequest, GetShardRecoveryPointResponse, InitiateShardTransferRequest, UpdateShardCutoffPointRequest, WaitForShardStateRequest, }; use storage::content_manager::toc::TableOfContent; use storage::rbac::{Access, AccessRequirements, CollectionPass}; use tonic::{Request, Response, Status}; use super::validate_and_log; use crate::tonic::api::collections_common::get; const FULL_ACCESS: Access = Access::full("Internal API"); fn full_access_pass(collection_name: &str) -> Result, Status> { FULL_ACCESS .check_collection_access(collection_name, AccessRequirements::new()) .map_err(Status::from) } pub struct CollectionsInternalService { toc: Arc, } impl CollectionsInternalService { pub fn new(toc: Arc) -> Self { Self { toc } } } #[tonic::async_trait] impl CollectionsInternal for CollectionsInternalService { async fn get( &self, request: Request, ) -> Result, Status> { validate_and_log(request.get_ref()); let GetCollectionInfoRequestInternal { get_collection_info_request, shard_id, } = request.into_inner(); let get_collection_info_request = get_collection_info_request .ok_or_else(|| Status::invalid_argument("GetCollectionInfoRequest is missing"))?; get( self.toc.as_ref(), get_collection_info_request, FULL_ACCESS.clone(), Some(shard_id), ) .await } async fn initiate( &self, request: Request, ) -> Result, Status> { // TODO: Ensure cancel safety! validate_and_log(request.get_ref()); let timing = Instant::now(); let InitiateShardTransferRequest { collection_name, shard_id, } = request.into_inner(); // TODO: Ensure cancel safety! self.toc .initiate_receiving_shard(collection_name, shard_id) .await?; let response = CollectionOperationResponse { result: true, time: timing.elapsed().as_secs_f64(), }; Ok(Response::new(response)) } async fn wait_for_shard_state( &self, request: Request, ) -> Result, Status> { let request = request.into_inner(); validate_and_log(&request); let timing = Instant::now(); let WaitForShardStateRequest { collection_name, shard_id, state, timeout, } = request; let state = state.try_into()?; let timeout = Duration::from_secs(timeout); let collection_read = self .toc .get_collection(&full_access_pass(&collection_name)?) .await .map_err(|err| { Status::not_found(format!( "Collection {collection_name} could not be found: {err}" )) })?; // Wait for replica state collection_read .wait_local_shard_replica_state(shard_id, state, timeout) .await .map_err(|err| { Status::aborted(format!( "Failed to wait for shard {shard_id} to get into {state:?} state: {err}" )) })?; let response = CollectionOperationResponse { result: true, time: timing.elapsed().as_secs_f64(), }; Ok(Response::new(response)) } async fn get_shard_recovery_point( &self, request: Request, ) -> Result, Status> { validate_and_log(request.get_ref()); let timing = Instant::now(); let GetShardRecoveryPointRequest { collection_name, shard_id, } = request.into_inner(); let collection_read = self .toc .get_collection(&full_access_pass(&collection_name)?) .await .map_err(|err| { Status::not_found(format!( "Collection {collection_name} could not be found: {err}" )) })?; // Get shard recovery point let recovery_point = collection_read .shard_recovery_point(shard_id) .await .map_err(|err| { Status::internal(format!( "Failed to get recovery point for shard {shard_id}: {err}" )) })?; let response = GetShardRecoveryPointResponse { recovery_point: Some(recovery_point.into()), time: timing.elapsed().as_secs_f64(), }; Ok(Response::new(response)) } async fn update_shard_cutoff_point( &self, request: Request, ) -> Result, Status> { validate_and_log(request.get_ref()); let timing = Instant::now(); let UpdateShardCutoffPointRequest { collection_name, shard_id, cutoff, } = request.into_inner(); let cutoff = cutoff.ok_or_else(|| Status::invalid_argument("Missing cutoff point"))?; let collection_read = self .toc .get_collection(&full_access_pass(&collection_name)?) .await .map_err(|err| { Status::not_found(format!( "Collection {collection_name} could not be found: {err}" )) })?; // Set the shard cutoff point collection_read .update_shard_cutoff_point(shard_id, &cutoff.try_into()?) .await .map_err(|err| { Status::internal(format!( "Failed to set shard cutoff point for shard {shard_id}: {err}" )) })?; let response = CollectionOperationResponse { result: true, time: timing.elapsed().as_secs_f64(), }; Ok(Response::new(response)) } }