Skip to content

Commit e0727b8

Browse files
committed
feat: implemented module server
1 parent 9b6a3a1 commit e0727b8

4 files changed

Lines changed: 145 additions & 12 deletions

File tree

gateway/src/client/module_service_client.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use tonic::transport::{Channel, Endpoint};
33
use tucana::sagittarius_rails::module_service_client::ModuleServiceClient;
44
use tucana::sagittarius_rails::{ModuleUpdateRequest, ModuleUpdateResponse};
55

6+
#[derive(Clone)]
67
pub struct SagittariusRailsModuleServiceClient {
78
inner: ModuleServiceClient<Channel>,
89
}
@@ -19,10 +20,10 @@ impl SagittariusRailsModuleServiceClient {
1920
}
2021

2122
pub async fn update(
22-
&mut self,
23+
&self,
2324
request: ModuleUpdateRequest,
2425
) -> Result<tonic::Response<ModuleUpdateResponse>, tonic::Status> {
2526
log::debug!("Proxying a module update request.");
26-
self.inner.update(request).await
27+
self.inner.clone().update(request).await
2728
}
2829
}

gateway/src/client/token_service_client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl SagittariusRailsTokenServiceClient {
2828
&mut self,
2929
request: TokenVerifyRequest,
3030
) -> Result<tonic::Response<TokenVerifyResponse>, tonic::Status> {
31-
self.inner.update(request).await
31+
self.inner.verify(request).await
3232
}
3333

3434
pub async fn validate_token(&mut self, token: String) -> RuntimeVerificationStatus {

gateway/src/config/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ impl Default for Backend {
4545
}
4646
}
4747

48-
4948
impl Config {
5049
pub fn new() -> Self {
5150
Self::try_new()
Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,164 @@
11
#![allow(dead_code)]
22

3+
use crate::client::module_service_client::SagittariusRailsModuleServiceClient;
34
use std::pin::Pin;
5+
use std::sync::Arc;
46

7+
use tokio::sync::{Mutex, OwnedMutexGuard, mpsc};
58
use tonic::codegen::tokio_stream::Stream;
9+
use tonic::codegen::tokio_stream::wrappers::ReceiverStream;
10+
use tonic::metadata::MetadataMap;
11+
use tonic::{Extensions, Response, Status};
612
use tucana::sagittarius_gateway::module_service_server::ModuleService;
7-
use tucana::sagittarius_gateway::{ModuleUpdateRequest, ModuleUpdateResponse};
13+
use tucana::sagittarius_gateway::{
14+
ModuleConfigurationPushRequest, ModuleConfigurationPushResponse, ModuleConfigurationRequest,
15+
ModuleConfigurationResponse, ModuleUpdateRequest, ModuleUpdateResponse,
16+
};
17+
use tucana::sagittarius_rails::{
18+
ModuleUpdateRequest as RailsModuleUpdateRequest,
19+
ModuleUpdateResponse as RailsModuleUpdateResponse,
20+
};
821

9-
pub struct SagittariusModuleService {}
22+
const MODULE_CONFIGURATION_QUEUE_CAPACITY: usize = 1024;
1023

11-
type ModuleConfigurationsStream =
12-
Pin<Box<dyn Stream<Item = Result<ModuleUpdateResponse, tonic::Status>> + Send + 'static>>;
24+
pub struct SagittariusModuleService {
25+
client: SagittariusRailsModuleServiceClient,
26+
// Push is unary while Aquila receives module configurations through a
27+
// long-lived stream, so the service needs a queue between those RPC shapes.
28+
stream_tx: mpsc::Sender<ModuleConfigurationResponse>,
29+
// There is one shared configuration queue for Aquila. Keeping the receiver
30+
// guarded here makes that single-consumer assumption explicit at the boundary.
31+
stream_rx: Arc<Mutex<mpsc::Receiver<ModuleConfigurationResponse>>>,
32+
}
33+
34+
type ModuleConfigurationsStream = Pin<
35+
Box<dyn Stream<Item = Result<ModuleConfigurationResponse, tonic::Status>> + Send + 'static>,
36+
>;
37+
38+
type ModuleConfigurationSender = mpsc::Sender<Result<ModuleConfigurationResponse, tonic::Status>>;
39+
40+
impl SagittariusModuleService {
41+
pub fn new(client: SagittariusRailsModuleServiceClient) -> Self {
42+
let (stream_tx, stream_rx) = mpsc::channel(MODULE_CONFIGURATION_QUEUE_CAPACITY);
43+
44+
Self {
45+
client,
46+
stream_tx,
47+
stream_rx: Arc::new(Mutex::new(stream_rx)),
48+
}
49+
}
50+
51+
async fn run_configurations_stream(
52+
mut queued_configurations: OwnedMutexGuard<mpsc::Receiver<ModuleConfigurationResponse>>,
53+
outgoing_stream: ModuleConfigurationSender,
54+
) {
55+
while let Some(configuration) = queued_configurations.recv().await {
56+
if Self::send_to_aquila_stream(&outgoing_stream, configuration)
57+
.await
58+
.is_err()
59+
{
60+
log::info!("Aquila module configuration stream closed.");
61+
return;
62+
}
63+
}
64+
65+
log::info!("Module configuration queue closed.");
66+
}
67+
68+
async fn send_to_aquila_stream(
69+
outgoing_stream: &ModuleConfigurationSender,
70+
response: ModuleConfigurationResponse,
71+
) -> Result<(), mpsc::error::SendError<Result<ModuleConfigurationResponse, tonic::Status>>>
72+
{
73+
outgoing_stream.send(Ok(response)).await
74+
}
75+
76+
fn to_rails_update_request(request: ModuleUpdateRequest) -> RailsModuleUpdateRequest {
77+
RailsModuleUpdateRequest {
78+
modules: request.modules,
79+
available_defintition_soruces: Vec::new(),
80+
}
81+
}
82+
83+
fn from_rails_update_response(response: RailsModuleUpdateResponse) -> ModuleUpdateResponse {
84+
ModuleUpdateResponse {
85+
success: response.success,
86+
error: response.error,
87+
}
88+
}
89+
90+
fn empty_push_response() -> tonic::Response<ModuleConfigurationPushResponse> {
91+
Response::from_parts(
92+
MetadataMap::new(),
93+
ModuleConfigurationPushResponse {},
94+
Extensions::new(),
95+
)
96+
}
97+
98+
fn extract_configuration_response(
99+
request: tonic::Request<ModuleConfigurationPushRequest>,
100+
) -> Option<ModuleConfigurationResponse> {
101+
request.into_inner().response
102+
}
103+
}
13104

14105
#[tonic::async_trait]
15106
impl ModuleService for SagittariusModuleService {
107+
// Aquila sends module metadata as a unary update, while Rails owns the
108+
// backing state. This method stays as a direct proxy instead of using the
109+
// stream queue because there is no long-lived Aquila response involved.
16110
async fn update(
17111
&self,
18-
_request: tonic::Request<ModuleUpdateRequest>,
112+
request: tonic::Request<ModuleUpdateRequest>,
19113
) -> Result<tonic::Response<ModuleUpdateResponse>, tonic::Status> {
20-
todo!()
114+
let rails_request = Self::to_rails_update_request(request.into_inner());
115+
let rails_response = self.client.update(rails_request).await?.into_inner();
116+
117+
Ok(Response::new(Self::from_rails_update_response(
118+
rails_response,
119+
)))
21120
}
22121

23122
type ConfigurationsStream = ModuleConfigurationsStream;
24123

124+
// Aquila owns the long-lived configuration stream, so this method starts the
125+
// bridge for module configuration pushes accepted by Sagittarius.
25126
async fn configurations(
26127
&self,
27-
_request: tonic::Request<ModuleUpdateRequest>,
128+
_request: tonic::Request<ModuleConfigurationRequest>,
28129
) -> Result<tonic::Response<Self::ConfigurationsStream>, tonic::Status> {
29-
todo!()
130+
// A second Aquila stream would compete for the same configuration queue
131+
// and make delivery semantics unclear, so reject it at connection time.
132+
let queued_configurations = Arc::clone(&self.stream_rx).try_lock_owned().map_err(|_| {
133+
Status::already_exists("Aquila module configuration stream is already connected")
134+
})?;
135+
let (response_tx, response_rx) = mpsc::channel(MODULE_CONFIGURATION_QUEUE_CAPACITY);
136+
137+
tokio::spawn(Self::run_configurations_stream(
138+
queued_configurations,
139+
response_tx,
140+
));
141+
142+
Ok(Response::new(Box::pin(ReceiverStream::new(response_rx))))
143+
}
144+
145+
// Sagittarius only needs an acknowledgment that the module configuration was
146+
// accepted into the bridge. Actual delivery happens through Aquila's stream.
147+
async fn push(
148+
&self,
149+
request: tonic::Request<ModuleConfigurationPushRequest>,
150+
) -> Result<tonic::Response<ModuleConfigurationPushResponse>, tonic::Status> {
151+
let Some(configuration) = Self::extract_configuration_response(request) else {
152+
return Ok(Self::empty_push_response());
153+
};
154+
155+
if let Err(err) = self.stream_tx.send(configuration).await {
156+
let error = format!("{:?}", err);
157+
log::error!("{}", &error);
158+
return Err(Status::internal(error));
159+
}
160+
161+
log::info!("Received module configuration request, will proxy request to Aquila.");
162+
Ok(Self::empty_push_response())
30163
}
31164
}

0 commit comments

Comments
 (0)