use tonic::Status;
use aion_proto::WireError;
use aion_proto::generated;
use super::WorkflowGrpcService;
use super::status::status_from_wire_error;
impl WorkflowGrpcService {
pub(super) async fn resolve_route(
&self,
workflow_id: Option<aion_proto::ProtoWorkflowId>,
metadata: &tonic::metadata::MetadataMap,
request: crate::routing::ForwardRequest,
) -> RouteResolution {
use crate::routing::{RouteDecision, route_mutation};
let Some(cluster_store) = self.state.cluster_store() else {
return RouteResolution::Local;
};
let Some(proto) = workflow_id else {
return RouteResolution::Local;
};
let Ok(workflow_id) = aion_core::WorkflowId::try_from(proto) else {
return RouteResolution::Local;
};
let directory = self
.state
.shard_directory()
.map(|directory| directory.as_ref() as &dyn crate::routing::ShardDirectory);
match route_mutation(Some(cluster_store.as_ref()), directory, &workflow_id) {
RouteDecision::Local => RouteResolution::Local,
RouteDecision::NotOwner { shard } => RouteResolution::Reject(not_owner_status(shard)),
RouteDecision::Forward { owner, shard } => {
self.forward_or_reject(owner, shard, metadata, request)
.await
}
}
}
async fn forward_or_reject(
&self,
owner: crate::routing::NodeRef,
shard: usize,
metadata: &tonic::metadata::MetadataMap,
request: crate::routing::ForwardRequest,
) -> RouteResolution {
use crate::routing::{MAX_FORWARD_HOPS, current_hops};
if current_hops(metadata) >= MAX_FORWARD_HOPS {
return RouteResolution::Reject(not_owner_status(shard));
}
let Some(target) = owner.grpc_addr else {
return RouteResolution::Reject(not_owner_status(shard));
};
let Some(forwarder) = self.state.request_forwarder() else {
return RouteResolution::Reject(not_owner_status(shard));
};
match forwarder.forward(target, metadata.clone(), request).await {
Ok(reply) => RouteResolution::Reply(reply),
Err(_status) => RouteResolution::Reject(not_owner_status(shard)),
}
}
fn start_placement(&self) -> Option<aion_core::WorkflowId> {
use crate::routing::{RemintOutcome, route_start};
match route_start(self.state.cluster_store().map(AsRef::as_ref)) {
RemintOutcome::UseId(workflow_id) => Some(workflow_id),
RemintOutcome::EngineMint => None,
}
}
pub(super) async fn resolve_start(
&self,
request: &generated::StartWorkflowRequest,
metadata: &tonic::metadata::MetadataMap,
) -> StartResolution {
use crate::routing::{SteerDecision, route_start_steered};
let routing_key = request.routing_key.as_deref().filter(|key| !key.is_empty());
let Some(routing_key) = routing_key else {
return StartResolution::Local(self.start_placement());
};
let Some(cluster_store) = self.state.cluster_store() else {
return StartResolution::Local(None);
};
let directory = self
.state
.shard_directory()
.map(|directory| directory.as_ref() as &dyn crate::routing::ShardDirectory);
match route_start_steered(cluster_store.as_ref(), directory, routing_key) {
SteerDecision::Local(workflow_id) => StartResolution::Local(Some(workflow_id)),
SteerDecision::NotOwner { shard } => StartResolution::Reject(not_owner_status(shard)),
SteerDecision::Forward { owner, shard } => {
self.forward_or_reject_start(owner, shard, metadata, request.clone())
.await
}
}
}
async fn forward_or_reject_start(
&self,
owner: crate::routing::NodeRef,
shard: usize,
metadata: &tonic::metadata::MetadataMap,
request: generated::StartWorkflowRequest,
) -> StartResolution {
use crate::routing::{ForwardReply, ForwardRequest, MAX_FORWARD_HOPS, current_hops};
if current_hops(metadata) >= MAX_FORWARD_HOPS {
return StartResolution::Reject(not_owner_status(shard));
}
let Some(target) = owner.grpc_addr else {
return StartResolution::Reject(not_owner_status(shard));
};
let Some(forwarder) = self.state.request_forwarder() else {
return StartResolution::Reject(not_owner_status(shard));
};
match forwarder
.forward(target, metadata.clone(), ForwardRequest::Start(request))
.await
{
Ok(ForwardReply::Start(reply)) => StartResolution::Reply(reply),
Ok(_) => {
StartResolution::Reject(Status::internal("forwarder returned a mismatched reply"))
}
Err(_status) => StartResolution::Reject(not_owner_status(shard)),
}
}
}
pub(super) enum StartResolution {
Local(Option<aion_core::WorkflowId>),
Reply(generated::StartWorkflowResponse),
Reject(Status),
}
pub(super) enum RouteResolution {
Local,
Reply(crate::routing::ForwardReply),
Reject(Status),
}
fn not_owner_status(shard: usize) -> Status {
let wire = WireError::not_owner(format!(
"workflow shard {shard} is owned by another cluster node"
))
.with_error_type("NotOwner");
status_from_wire_error(wire)
}