oxigeo_distributed/flight/
client.rs1use crate::error::{DistributedError, Result};
7use crate::flight::wire::{self, ExecuteTaskResponse};
8use crate::task::Task;
9use arrow::record_batch::RecordBatch;
10use arrow_flight::{Action, HandshakeRequest, Ticket, flight_service_client::FlightServiceClient};
11use bytes::Bytes;
12use futures::StreamExt;
13use std::time::Duration;
14use tonic::transport::{Channel, Endpoint};
15use tracing::{debug, info, warn};
16
17pub struct FlightClient {
19 client: FlightServiceClient<Channel>,
21 address: String,
23}
24
25impl FlightClient {
26 pub async fn new(address: String) -> Result<Self> {
28 info!("Connecting to Flight server at {}", address);
29
30 let endpoint = Endpoint::from_shared(address.clone())
31 .map_err(|e| DistributedError::worker_connection(format!("Invalid endpoint: {}", e)))?
32 .connect_timeout(Duration::from_secs(10))
33 .timeout(Duration::from_secs(60))
34 .tcp_keepalive(Some(Duration::from_secs(30)))
35 .http2_keep_alive_interval(Duration::from_secs(30))
36 .keep_alive_timeout(Duration::from_secs(10));
37
38 let channel = endpoint.connect().await.map_err(|e| {
39 DistributedError::worker_connection(format!("Connection failed: {}", e))
40 })?;
41
42 let client = FlightServiceClient::new(channel);
43
44 Ok(Self { client, address })
45 }
46
47 pub async fn handshake(&mut self) -> Result<()> {
49 debug!("Performing handshake with {}", self.address);
50
51 let request = tonic::Request::new(futures::stream::once(async {
52 HandshakeRequest {
53 protocol_version: 0,
54 payload: Bytes::new(),
55 }
56 }));
57
58 let mut response_stream = self
59 .client
60 .handshake(request)
61 .await
62 .map_err(|e| DistributedError::flight_rpc(format!("Handshake failed: {}", e)))?
63 .into_inner();
64
65 while let Some(response) = response_stream.next().await {
67 let _handshake_response = response
68 .map_err(|e| DistributedError::flight_rpc(format!("Handshake error: {}", e)))?;
69 debug!("Handshake successful");
70 }
71
72 Ok(())
73 }
74
75 pub async fn get_data(&mut self, ticket: String) -> Result<Vec<RecordBatch>> {
77 info!("Fetching data for ticket: {}", ticket);
78
79 let ticket = Ticket {
80 ticket: Bytes::from(ticket),
81 };
82
83 let request = tonic::Request::new(ticket);
84
85 let mut stream = self
86 .client
87 .do_get(request)
88 .await
89 .map_err(|e| DistributedError::flight_rpc(format!("DoGet failed: {}", e)))?
90 .into_inner();
91
92 let mut flight_data_vec = Vec::new();
93
94 while let Some(data_result) = stream.next().await {
95 flight_data_vec.push(
96 data_result
97 .map_err(|e| DistributedError::flight_rpc(format!("Stream error: {}", e)))?,
98 );
99 }
100
101 let batches = arrow_flight::utils::flight_data_to_batches(&flight_data_vec)
103 .map_err(|e| DistributedError::arrow(format!("Failed to decode batches: {}", e)))?;
104
105 info!("Received {} batches", batches.len());
106 Ok(batches)
107 }
108
109 pub async fn put_data(&mut self, batches: Vec<RecordBatch>) -> Result<()> {
111 info!("Sending {} batches to server", batches.len());
112
113 if batches.is_empty() {
114 return Err(DistributedError::flight_rpc("No batches to send"));
115 }
116
117 let flight_data_vec =
119 arrow_flight::utils::batches_to_flight_data(batches[0].schema().as_ref(), batches)
120 .map_err(|e| DistributedError::arrow(format!("Failed to encode batches: {}", e)))?;
121
122 let request = tonic::Request::new(futures::stream::iter(flight_data_vec));
123
124 let mut response_stream = self
125 .client
126 .do_put(request)
127 .await
128 .map_err(|e| DistributedError::flight_rpc(format!("DoPut failed: {}", e)))?
129 .into_inner();
130
131 while let Some(result) = response_stream.next().await {
133 let _put_result =
134 result.map_err(|e| DistributedError::flight_rpc(format!("Put error: {}", e)))?;
135 }
136
137 info!("Data sent successfully");
138 Ok(())
139 }
140
141 pub async fn do_action(&mut self, action_type: String, body: Bytes) -> Result<Vec<Bytes>> {
143 debug!("Executing action: {}", action_type);
144
145 let action = Action {
146 r#type: action_type.clone(),
147 body,
148 };
149
150 let request = tonic::Request::new(action);
151
152 let mut stream = self
153 .client
154 .do_action(request)
155 .await
156 .map_err(|e| DistributedError::flight_rpc(format!("DoAction failed: {}", e)))?
157 .into_inner();
158
159 let mut results = Vec::new();
160
161 while let Some(result) = stream.next().await {
162 let action_result =
163 result.map_err(|e| DistributedError::flight_rpc(format!("Action error: {}", e)))?;
164 results.push(action_result.body);
165 }
166
167 debug!(
168 "Action {} completed with {} results",
169 action_type,
170 results.len()
171 );
172 Ok(results)
173 }
174
175 pub async fn execute_task(
183 &mut self,
184 task: &Task,
185 input: Option<&RecordBatch>,
186 ) -> Result<(ExecuteTaskResponse, Option<RecordBatch>)> {
187 let body = wire::encode_execute_request(task, input)?;
188 let results = self
189 .do_action(wire::EXECUTE_TASK_ACTION.to_string(), body)
190 .await?;
191
192 let payload = results.first().ok_or_else(|| {
193 DistributedError::flight_rpc("execute_task returned no result payload")
194 })?;
195
196 wire::decode_execute_response(payload)
197 }
198
199 pub async fn list_tickets(&mut self) -> Result<Vec<String>> {
201 let results = self
202 .do_action("list_tickets".to_string(), Bytes::new())
203 .await?;
204
205 if results.is_empty() {
206 return Ok(Vec::new());
207 }
208
209 let tickets: Vec<String> = serde_json::from_slice(&results[0]).map_err(|e| {
210 DistributedError::flight_rpc(format!("Failed to deserialize tickets: {}", e))
211 })?;
212
213 Ok(tickets)
214 }
215
216 pub async fn remove_ticket(&mut self, ticket: String) -> Result<()> {
218 let body = Bytes::from(ticket.clone());
219 let _results = self.do_action("remove_ticket".to_string(), body).await?;
220
221 info!("Removed ticket: {}", ticket);
222 Ok(())
223 }
224
225 pub fn address(&self) -> &str {
227 &self.address
228 }
229
230 pub async fn health_check(&mut self) -> Result<bool> {
232 match self.handshake().await {
233 Ok(_) => Ok(true),
234 Err(e) => {
235 warn!("Health check failed: {}", e);
236 Ok(false)
237 }
238 }
239 }
240}
241
242pub struct FlightClientPool {
244 clients: Vec<FlightClient>,
246 max_size: usize,
248}
249
250impl FlightClientPool {
251 pub fn new(max_size: usize) -> Self {
253 Self {
254 clients: Vec::new(),
255 max_size,
256 }
257 }
258
259 pub async fn add_client(&mut self, address: String) -> Result<()> {
261 if self.clients.len() >= self.max_size {
262 return Err(DistributedError::worker_connection(
263 "Pool is at maximum capacity",
264 ));
265 }
266
267 let client = FlightClient::new(address).await?;
268 self.clients.push(client);
269 Ok(())
270 }
271
272 pub fn get_client(&mut self) -> Result<&mut FlightClient> {
274 if self.clients.is_empty() {
275 return Err(DistributedError::worker_connection("No clients available"));
276 }
277
278 self.clients.rotate_left(1);
280 let idx = self.clients.len() - 1;
281 Ok(&mut self.clients[idx])
282 }
283
284 pub fn size(&self) -> usize {
286 self.clients.len()
287 }
288
289 pub async fn health_check_all(&mut self) -> Result<Vec<bool>> {
291 let mut results = Vec::new();
292
293 for client in &mut self.clients {
294 let is_healthy = client.health_check().await.unwrap_or(false);
295 results.push(is_healthy);
296 }
297
298 Ok(results)
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_client_pool() {
308 let pool = FlightClientPool::new(5);
309 assert_eq!(pool.size(), 0);
310 assert_eq!(pool.max_size, 5);
311 }
312
313 #[tokio::test]
314 async fn test_client_creation_fails_for_invalid_address() {
315 let result = FlightClient::new("invalid://address".to_string()).await;
316 assert!(result.is_err());
317 }
318
319 #[test]
320 fn test_pool_get_client_empty() {
321 let mut pool = FlightClientPool::new(5);
322 let result = pool.get_client();
323 assert!(result.is_err());
324 }
325}