1use std::collections::{HashMap, VecDeque};
5use std::pin::Pin;
6use std::sync::Arc;
7
8use std::time::Duration;
9
10use display_error_chain::ErrorChainExt;
11use slim_config::server::ServerConfig;
12use slim_session::subscription_manager::SubscriptionManager;
13use tokio::sync::mpsc;
14use tokio::task::JoinHandle;
15use tokio_stream::{Stream, StreamExt, wrappers::ReceiverStream};
16use tokio_util::sync::CancellationToken;
17use tonic::{Request, Response, Status};
18use tracing::{debug, error, info};
19
20use crate::api::proto::api::v1::control_message::Payload;
21use crate::api::proto::api::v1::controller_service_server::ControllerServiceServer;
22use crate::api::proto::api::v1::{
23 self, ConnectionDetails, ConnectionDirection, ConnectionListResponse, ConnectionType,
24 RouteListResponse,
25};
26use crate::api::proto::api::v1::{
27 ConnectionEntry, ControlMessage, RouteEntry,
28 controller_service_client::ControllerServiceClient,
29 controller_service_server::ControllerService as GrpcControllerService,
30};
31use crate::errors::ControllerError;
32use prost_types::Struct;
33use slim_config::client::{ClientConfig, TransportChannel};
34use slim_datapath::api::{
35 MessageType::Link as LinkMessageType, MessageType::Subscribe,
36 MessageType::SubscriptionAck as SubscriptionAckType, MessageType::Unsubscribe,
37 ProtoMessage as DataPlaneMessage,
38};
39use slim_datapath::api::{NameId, ProtoName};
40use slim_datapath::message_processing::MessageProcessor;
41use slim_datapath::messages::utils::SlimHeaderFlags;
42use slim_datapath::tables::{ConnType, SubscriptionTable};
43
44type TxChannel = mpsc::Sender<Result<ControlMessage, Status>>;
45type TxChannels = HashMap<String, TxChannel>;
46
47const MAX_QUEUED_NOTIFICATIONS: usize = 1000;
49
50const SUBSCRIPTION_ACK_TIMEOUT: Duration = Duration::from_secs(30);
52
53#[derive(Clone)]
55pub struct ControlPlaneSettings {
56 pub id: String,
58 pub group_name: Option<String>,
60 pub servers: Vec<ServerConfig>,
62 pub clients: Vec<ClientConfig>,
64 pub message_processor: MessageProcessor,
66 pub connection_details: Vec<ConnectionDetails>,
69}
70
71struct ControllerServiceInternal {
76 id: String,
78
79 group_name: Option<String>,
81
82 message_processor: MessageProcessor,
84
85 tx_slim: mpsc::Sender<Result<DataPlaneMessage, Status>>,
87
88 tx_channels: parking_lot::RwLock<TxChannels>,
90
91 cancellation_tokens: parking_lot::RwLock<HashMap<String, CancellationToken>>,
93
94 drain_watch: parking_lot::RwLock<Option<drain::Watch>>,
96
97 pending_notifications: Arc<parking_lot::Mutex<VecDeque<ControlMessage>>>,
99
100 subscription_manager: SubscriptionManager,
102
103 connection_details: Vec<ConnectionDetails>,
105
106 route_subscription_ids: parking_lot::Mutex<HashMap<(ProtoName, u64), u64>>,
108
109 link_id_to_conn_id: parking_lot::RwLock<HashMap<String, u64>>,
112
113 stream_handles: parking_lot::Mutex<HashMap<String, tokio::task::JoinHandle<()>>>,
115}
116
117#[derive(Clone)]
118struct ControllerService {
119 inner: Arc<ControllerServiceInternal>,
121}
122
123pub struct ControlPlane {
125 servers: Vec<ServerConfig>,
127
128 clients: Vec<ClientConfig>,
130
131 drain_signal: parking_lot::RwLock<Option<drain::Signal>>,
133
134 controller: ControllerService,
136
137 rx_slim_option: Option<mpsc::Receiver<Result<DataPlaneMessage, Status>>>,
140}
141
142impl Drop for ControlPlane {
145 fn drop(&mut self) {
146 for (_endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
148 token.cancel();
149 }
150 }
151}
152
153pub(crate) fn from_server_config(server_config: &ServerConfig) -> ConnectionDetails {
154 let mut endpoint = server_config.endpoint.clone();
155 let mut external_endpoint = None;
156 let mut spire_socket_path = None;
157 let mut trust_domain = None;
158 let mut remaining_fields = std::collections::BTreeMap::new();
159
160 if let Some(m) = &server_config.metadata {
161 for (k, v) in &m.inner {
162 match k.as_str() {
163 "local_endpoint" => {
164 if let Some(s) = v.as_str()
165 && !s.is_empty()
166 {
167 endpoint = s.to_string();
168 }
169 }
170 "external_endpoint" => {
171 if let Some(s) = v.as_str()
172 && !s.is_empty()
173 {
174 external_endpoint = Some(s.to_string());
175 }
176 }
177 "spire_socket_path" => {
178 if let Some(s) = v.as_str()
179 && !s.is_empty()
180 {
181 spire_socket_path = Some(s.to_string());
182 }
183 }
184 "trust_domain" => {
185 if let Some(s) = v.as_str()
186 && !s.is_empty()
187 {
188 trust_domain = Some(s.to_string());
189 }
190 }
191 _ => {
192 remaining_fields.insert(k.clone(), prost_types::Value::from(v));
193 }
194 }
195 }
196 }
197
198 let spire_mtls = if !server_config.tls_setting.insecure || spire_socket_path.is_some() {
199 Some(v1::connection_details::SpireMtls {
200 socket_path: spire_socket_path.unwrap_or_default(),
201 trust_domain,
202 })
203 } else {
204 None
205 };
206
207 let metadata = if remaining_fields.is_empty() {
208 None
209 } else {
210 Some(Struct {
211 fields: remaining_fields,
212 })
213 };
214
215 ConnectionDetails {
216 endpoint,
217 external_endpoint,
218 spire_mtls,
219 metadata,
220 }
221}
222
223impl ControlPlane {
225 pub fn new(config: ControlPlaneSettings) -> Self {
238 let (_, tx_slim, rx_slim) = config
240 .message_processor
241 .register_local_connection(true)
242 .unwrap();
243
244 let (signal, watch) = drain::channel();
245
246 ControlPlane {
247 servers: config.servers,
248 clients: config.clients,
249 controller: ControllerService {
250 inner: Arc::new(ControllerServiceInternal {
251 id: config.id,
252 group_name: config.group_name,
253 message_processor: config.message_processor,
254 subscription_manager: SubscriptionManager::new(tx_slim.clone()),
255 tx_slim,
256 tx_channels: parking_lot::RwLock::new(HashMap::new()),
257 cancellation_tokens: parking_lot::RwLock::new(HashMap::new()),
258 drain_watch: parking_lot::RwLock::new(Some(watch)),
259 pending_notifications: Arc::new(parking_lot::Mutex::new(VecDeque::new())),
260 connection_details: config.connection_details,
261 route_subscription_ids: parking_lot::Mutex::new(HashMap::new()),
262 link_id_to_conn_id: parking_lot::RwLock::new(HashMap::new()),
263 stream_handles: parking_lot::Mutex::new(HashMap::new()),
264 }),
265 },
266 drain_signal: parking_lot::RwLock::new(Some(signal)),
267 rx_slim_option: Some(rx_slim),
268 }
269 }
270
271 pub fn with_clients(mut self, clients: Vec<ClientConfig>) -> Self {
273 self.clients = clients;
274 self
275 }
276
277 pub fn with_servers(mut self, servers: Vec<ServerConfig>) -> Self {
279 self.servers = servers;
280 self
281 }
282
283 pub async fn run(&mut self) -> Result<(), ControllerError> {
290 let rx = self
291 .rx_slim_option
292 .take()
293 .ok_or(ControllerError::AlreadyStarted)?;
294
295 let servers = self.servers.clone();
297 let clients = self.clients.clone();
298
299 for server in servers {
301 self.run_server(server).await?;
302 }
303
304 for client in clients {
306 self.run_client(client).await?;
307 }
308
309 self.listen_from_data_plane(rx).await?;
310
311 Ok(())
312 }
313
314 pub async fn deregister(&self) -> Result<(), ControllerError> {
315 let node_id = self.controller.inner.id.clone();
316 let deregister_msg = ControlMessage {
317 message_id: uuid::Uuid::new_v4().to_string(),
318 payload: Some(Payload::DeregisterNodeRequest(v1::DeregisterNodeRequest {
319 node: Some(v1::Node { id: node_id }),
320 })),
321 };
322 let channels: Vec<(String, TxChannel)> = self
323 .controller
324 .inner
325 .tx_channels
326 .read()
327 .iter()
328 .map(|(ep, tx)| (ep.clone(), tx.clone()))
329 .collect();
330 for (endpoint, tx) in channels {
331 if let Err(e) = tx.send(Ok(deregister_msg.clone())).await {
332 error!(%endpoint, error = %e, "failed to send deregister request");
333 }
334 }
335 Ok(())
336 }
337
338 pub async fn shutdown(&self) -> Result<(), ControllerError> {
339 let signal = self
341 .drain_signal
342 .write()
343 .take()
344 .ok_or(ControllerError::AlreadyStopped)?;
345
346 self.controller
348 .inner
349 .cancellation_tokens
350 .write()
351 .drain()
352 .for_each(|(endpoint, token)| {
353 info!(%endpoint, "stopping");
354 token.cancel();
355 });
356
357 self.controller.inner.drain_watch.write().take();
359
360 signal.drain().await;
362
363 Ok(())
364 }
365
366 async fn listen_from_data_plane(
367 &mut self,
368 mut rx: mpsc::Receiver<Result<DataPlaneMessage, Status>>,
369 ) -> Result<(), ControllerError> {
370 let cancellation_token = CancellationToken::new();
371 let cancellation_token_clone = cancellation_token.clone();
372
373 self.controller
374 .inner
375 .cancellation_tokens
376 .write()
377 .insert("DATA_PLANE".to_string(), cancellation_token_clone);
378
379 let clients = self.clients.clone();
380 let controller = self.controller.clone();
381
382 let watch = self.controller.drain_watch()?;
384
385 debug!("Starting data plane listener");
386 tokio::spawn(async move {
387 let mut drain_fut = std::pin::pin!(watch.signaled());
388 loop {
389 tokio::select! {
390 next = rx.recv() => {
391 match next {
392 Some(res) => {
393 match res {
394 Ok(msg) => {
395 debug!("Received message {:?} from data plane, forwarding to control plane", msg);
396 match msg.get_type() {
397 Subscribe(_) => {
398 controller.handle_subscribe_message(msg.get_dst(), &clients).await;
399 }
400 Unsubscribe(_) => {
401 controller.handle_unsubscribe_message(msg.get_dst(), &clients).await;
402 }
403 SubscriptionAckType(_) => {
404 controller.inner.subscription_manager.resolve_ack(msg.get_subscription_ack());
405 }
406 LinkMessageType(link) => {
407 controller.handle_link_received(link, &clients).await;
408 }
409 _ => {
410 debug!("Ignoring unexpected message type from dataplane: {:?}", msg.get_type());
411 }
412 }
413 }
414 Err(e) => {
415 error!(error = %e.chain(), "received error from the data plane");
416 continue;
417 }
418 }
419 }
420 None => {
421 debug!("Data plane receiver channel closed.");
422 break;
423 }
424 }
425 }
426 _ = cancellation_token.cancelled() => {
427 debug!("shutting down stream on cancellation token");
428 break;
429 }
430 _ = &mut drain_fut => {
431 debug!("shutting down stream on drain");
432 break;
433 }
434 }
435 }
436 });
437 Ok(())
438 }
439
440 pub fn stop(&mut self) {
444 info!("stopping controller service");
445
446 for (endpoint, token) in self.controller.inner.cancellation_tokens.write().drain() {
448 info!(%endpoint, "stopping");
449 token.cancel();
450 }
451 }
452
453 async fn run_client(&mut self, client: ClientConfig) -> Result<(), ControllerError> {
457 if self
458 .controller
459 .inner
460 .cancellation_tokens
461 .read()
462 .contains_key(&client.endpoint)
463 {
464 return Err(ControllerError::ClientAlreadyRunning(client.endpoint));
465 }
466
467 let cancellation_token = CancellationToken::new();
468
469 let tx = self
470 .controller
471 .connect(client.clone(), cancellation_token.clone())
472 .await?;
473
474 self.controller
476 .inner
477 .cancellation_tokens
478 .write()
479 .insert(client.endpoint.clone(), cancellation_token);
480
481 self.controller
483 .inner
484 .tx_channels
485 .write()
486 .insert(client.endpoint.clone(), tx);
487
488 Ok(())
490 }
491
492 pub async fn run_server(&mut self, config: ServerConfig) -> Result<(), ControllerError> {
496 if self
498 .controller
499 .inner
500 .cancellation_tokens
501 .read()
502 .contains_key(&config.endpoint)
503 {
504 error!(endpoint = config.endpoint, "server is already running",);
505 return Err(ControllerError::ServerAlreadyRunning(config.endpoint));
506 }
507
508 let token = config
509 .run_grpc_server(
510 &[ControllerServiceServer::new(self.controller.clone())],
511 self.controller.drain_watch()?,
512 )
513 .await?;
514
515 self.controller
517 .inner
518 .cancellation_tokens
519 .write()
520 .insert(config.endpoint.clone(), token.clone());
521
522 info!(%config.endpoint, "started controlplane server");
523
524 Ok(())
525 }
526}
527
528impl ControllerService {
529 fn resolve_connection_by_link_id(&self, link_id: &str) -> Result<Option<u64>, String> {
530 let cached = self.inner.link_id_to_conn_id.read().get(link_id).copied();
531 if let Some(conn_id) = cached {
532 if self
533 .inner
534 .message_processor
535 .connection_table()
536 .get(conn_id)
537 .is_some_and(|conn| conn.link_id().as_deref() == Some(link_id))
538 {
539 return Ok(Some(conn_id));
540 }
541 self.inner.link_id_to_conn_id.write().remove(link_id);
542 }
543
544 let mut resolved: Option<u64> = None;
545 self.inner
546 .message_processor
547 .connection_table()
548 .for_each(|id, conn| {
549 if conn.link_id().as_deref() == Some(link_id) && resolved.is_none() {
550 resolved = Some(id);
551 }
552 });
553
554 if let Some(conn_id) = resolved {
555 self.inner
556 .link_id_to_conn_id
557 .write()
558 .insert(link_id.to_string(), conn_id);
559 }
560
561 Ok(resolved)
562 }
563
564 fn disconnect_connection_by_link_id(&self, link_id: &str) -> Result<(), String> {
565 if link_id.trim().is_empty() {
566 return Err("link_id cannot be empty".to_string());
567 }
568
569 let conn_id = match self.resolve_connection_by_link_id(link_id)? {
570 Some(id) => id,
571 None => {
572 return Err(format!("Connection with link_id {} not found", link_id));
573 }
574 };
575
576 if let Err(e) = self.inner.message_processor.disconnect(conn_id) {
577 info!(
579 link_id = %link_id,
580 conn_id,
581 error = %e,
582 "Disconnect returned an error; continuing delete flow"
583 );
584 }
585
586 self.inner.link_id_to_conn_id.write().remove(link_id);
587 self.inner
588 .route_subscription_ids
589 .lock()
590 .retain(|(_name, cid), _| *cid != conn_id);
591
592 info!(link_id = %link_id, conn_id, "Successfully deleted connection by link_id");
593 Ok(())
594 }
595
596 fn resolve_route_connection(&self, route: &v1::Route) -> Result<Option<u64>, String> {
597 if let Some(link_id) = &route.link_id {
598 let trimmed = link_id.trim();
599 if !trimmed.is_empty() {
600 return self.resolve_connection_by_link_id(trimmed);
601 }
602 }
603
604 Ok(None)
605 }
606
607 async fn diff_connections(
608 &self,
609 desired_connections: &[v1::Connection],
610 ) -> Vec<v1::ConnectionAck> {
611 let mut connections_status = Vec::new();
612
613 let desired_link_ids: std::collections::HashSet<String> = desired_connections
614 .iter()
615 .map(|c| c.link_id.clone())
616 .collect();
617
618 let mut live_outgoing_link_ids: Vec<String> = Vec::new();
619 self.inner
620 .message_processor
621 .connection_table()
622 .for_each(|_id, conn| {
623 if conn.is_outgoing()
627 && conn.connection_type() == slim_datapath::tables::ConnType::Remote
628 && let Some(lid) = conn.link_id()
629 && !lid.is_empty()
630 {
631 live_outgoing_link_ids.push(lid);
632 }
633 });
634
635 for link_id in &live_outgoing_link_ids {
636 if desired_link_ids.contains(link_id) {
637 continue;
638 }
639 info!(link_id = %link_id, "desired state: removing connection");
640 let mut success = true;
641 let mut error_msg = String::new();
642 if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
643 success = false;
644 error_msg = err;
645 }
646 connections_status.push(v1::ConnectionAck {
647 link_id: link_id.clone(),
648 success,
649 error_msg,
650 });
651 }
652
653 for conn in desired_connections {
654 let link_id = &conn.link_id;
655 if link_id.is_empty() {
656 continue;
657 }
658
659 let already_exists = match self.resolve_connection_by_link_id(link_id) {
660 Ok(Some(_)) => true,
661 Ok(None) => false,
662 Err(err) => {
663 connections_status.push(v1::ConnectionAck {
664 link_id: link_id.clone(),
665 success: false,
666 error_msg: err,
667 });
668 continue;
669 }
670 };
671
672 if already_exists {
673 connections_status.push(v1::ConnectionAck {
674 link_id: link_id.clone(),
675 success: true,
676 error_msg: String::new(),
677 });
678 continue;
679 }
680
681 info!(?conn, "desired state: creating connection");
682 let mut success = true;
683 let mut error_msg = String::new();
684
685 match serde_json::from_str::<ClientConfig>(&conn.config_data) {
686 Err(e) => {
687 success = false;
688 error_msg = format!("Failed to parse config: {}", e);
689 }
690 Ok(client_config) => {
691 match self
692 .inner
693 .message_processor
694 .connect(client_config.clone(), None, None)
695 .await
696 {
697 Err(e) => {
698 success = false;
699 error_msg = format!("Connection failed: {}", e);
700 }
701 Ok(conn_id) => {
702 let requested_link_id = if client_config.link_id.trim().is_empty() {
703 String::new()
704 } else {
705 client_config.link_id.clone()
706 };
707 if !requested_link_id.is_empty() {
708 self.inner
709 .link_id_to_conn_id
710 .write()
711 .insert(requested_link_id, conn_id.1);
712 }
713 info!(
714 link_id = %link_id,
715 "Successfully created connection"
716 );
717 }
718 }
719 }
720 }
721
722 connections_status.push(v1::ConnectionAck {
723 link_id: link_id.clone(),
724 success,
725 error_msg,
726 });
727 }
728
729 connections_status
730 }
731
732 fn resolve_desired_routes<'a>(
733 &self,
734 desired_routes: &'a [v1::Route],
735 ) -> (HashMap<(ProtoName, u64), &'a v1::Route>, Vec<v1::RouteAck>) {
736 type SubKey = (ProtoName, u64);
737 let mut desired_subs: HashMap<SubKey, &v1::Route> = HashMap::new();
738 let mut failures: Vec<v1::RouteAck> = Vec::new();
739
740 for sub in desired_routes {
741 match self.resolve_route_connection(sub) {
742 Ok(Some(conn_id)) => {
743 let name = sub.name.clone().unwrap();
744 desired_subs.insert((name, conn_id), sub);
745 }
746 Ok(None) => {
747 failures.push(v1::RouteAck {
748 route: Some(sub.clone()),
749 success: false,
750 error_msg: "connection not found".to_string(),
751 });
752 }
753 Err(err) => {
754 failures.push(v1::RouteAck {
755 route: Some(sub.clone()),
756 success: false,
757 error_msg: err,
758 });
759 }
760 }
761 }
762
763 (desired_subs, failures)
764 }
765
766 async fn delete_stale_subscriptions(
767 &self,
768 desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
769 ) -> Vec<v1::RouteAck> {
770 let active_subs: Vec<((ProtoName, u64), u64)> = self
771 .inner
772 .route_subscription_ids
773 .lock()
774 .iter()
775 .map(|((name, conn_id), sub_id)| ((name.clone(), *conn_id), *sub_id))
776 .collect();
777
778 let stale: Vec<_> = active_subs
779 .into_iter()
780 .filter(|((name, conn_id), _)| !desired_subs.contains_key(&(name.clone(), *conn_id)))
781 .collect();
782
783 let futs = stale.iter().map(|((name, conn_id), sub_id)| {
784 let name = name.clone();
785 let conn_id = *conn_id;
786 let sub_id = *sub_id;
787 async move {
788 let conn_alive = self
789 .inner
790 .message_processor
791 .connection_table()
792 .get(conn_id)
793 .is_some();
794
795 let (success, error_msg) = if conn_alive {
796 let source = name.clone().with_id(0);
797 let unsub_msg = DataPlaneMessage::builder()
798 .source(source)
799 .destination(name.clone())
800 .identity("")
801 .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
802 .build_unsubscribe()
803 .unwrap();
804
805 match self
806 .send_unsubscribe_message_with_ack(unsub_msg, sub_id)
807 .await
808 {
809 Ok(()) => (true, String::new()),
810 Err(err) => (false, format!("Failed to unsubscribe: {}", err)),
811 }
812 } else {
813 (true, String::new())
814 };
815
816 (name, conn_id, success, error_msg)
817 }
818 });
819
820 let results = futures::future::join_all(futs).await;
821
822 let mut routes_status = Vec::with_capacity(results.len());
823 for (name, conn_id, success, error_msg) in results {
824 if success {
825 self.inner
826 .route_subscription_ids
827 .lock()
828 .remove(&(name.clone(), conn_id));
829 }
830
831 routes_status.push(v1::RouteAck {
832 route: Some(v1::Route {
833 name: Some(name.clone()),
834 link_id: None,
835 direction: None,
836 }),
837 success,
838 error_msg,
839 });
840 }
841
842 routes_status
843 }
844
845 async fn create_new_subscriptions(
846 &self,
847 desired_subs: &HashMap<(ProtoName, u64), &v1::Route>,
848 ) -> Vec<v1::RouteAck> {
849 let mut routes_status = Vec::new();
850
851 let to_create: Vec<((ProtoName, u64), v1::Route)> = desired_subs
852 .iter()
853 .filter(|((name, conn_id), _)| {
854 !self
855 .inner
856 .route_subscription_ids
857 .lock()
858 .contains_key(&(name.clone(), *conn_id))
859 })
860 .map(|((name, conn_id), sub)| ((name.clone(), *conn_id), (*sub).clone()))
861 .collect();
862
863 for ((name, conn_id), sub) in desired_subs {
865 let dominated = to_create
866 .iter()
867 .any(|((n, c), _)| n == name && *c == *conn_id);
868 if !dominated {
869 routes_status.push(v1::RouteAck {
870 route: Some((*sub).clone()),
871 success: true,
872 error_msg: String::new(),
873 });
874 }
875 }
876
877 let futs = to_create.iter().map(|((name, conn_id), sub)| {
878 let name = name.clone();
879 let conn_id = *conn_id;
880 let sub = sub.clone();
881 async move {
882 let source = name.clone().with_id(0);
883 let sub_msg = DataPlaneMessage::builder()
884 .source(source)
885 .destination(name.clone())
886 .identity("")
887 .flags(SlimHeaderFlags::default().with_recv_from(conn_id))
888 .build_subscribe()
889 .unwrap();
890
891 let result = self.send_subscribe_message_with_ack(sub_msg).await;
892 (name, conn_id, sub, result)
893 }
894 });
895
896 let results = futures::future::join_all(futs).await;
897
898 for (name, conn_id, sub, result) in results {
899 let (success, error_msg) = match result {
900 Ok(subscription_id) => {
901 self.inner
902 .route_subscription_ids
903 .lock()
904 .insert((name, conn_id), subscription_id);
905 info!(?sub, "desired state: created route");
906 (true, String::new())
907 }
908 Err(err) => (false, format!("Failed to subscribe: {}", err)),
909 };
910
911 routes_status.push(v1::RouteAck {
912 route: Some(sub),
913 success,
914 error_msg,
915 });
916 }
917
918 routes_status
919 }
920
921 async fn handle_new_control_message(
923 &self,
924 msg: ControlMessage,
925 tx: &mpsc::Sender<Result<ControlMessage, Status>>,
926 ) -> Result<(), ControllerError> {
927 match msg.payload {
928 Some(ref payload) => {
929 match payload {
930 Payload::ConfigCommand(config) => {
931 let (connections_status, routes_status) = if config.reconcile {
932 let connections_status =
933 self.diff_connections(&config.connections_to_create).await;
934 let (desired_subs, mut routes_status) =
935 self.resolve_desired_routes(&config.routes_to_set);
936 routes_status
937 .extend(self.delete_stale_subscriptions(&desired_subs).await);
938 routes_status
939 .extend(self.create_new_subscriptions(&desired_subs).await);
940 (connections_status, routes_status)
941 } else {
942 let mut connections_status = Vec::new();
943 let mut routes_status = Vec::new();
944
945 for link_id in &config.connections_to_delete {
947 info!(link_id = %link_id, "received a connection to delete");
948 let mut connection_success = true;
949 let mut connection_error_msg = String::new();
950
951 if let Err(err) = self.disconnect_connection_by_link_id(link_id) {
952 connection_success = false;
953 connection_error_msg = err;
954 }
955
956 connections_status.push(v1::ConnectionAck {
957 link_id: link_id.clone(),
958 success: connection_success,
959 error_msg: connection_error_msg,
960 });
961 }
962
963 for conn in &config.connections_to_create {
965 info!(?conn, "received a connection to create");
966 let mut connection_success = true;
967 let mut connection_error_msg = String::new();
968
969 match serde_json::from_str::<ClientConfig>(&conn.config_data) {
970 Err(e) => {
971 connection_success = false;
972 connection_error_msg =
973 format!("Failed to parse config: {}", e);
974 }
975 Ok(client_config) => {
976 let client_endpoint = &client_config.endpoint;
977 let requested_link_id =
978 if client_config.link_id.trim().is_empty() {
979 String::new()
980 } else {
981 client_config.link_id.clone()
982 };
983 let mut existing_conn_for_link_id = false;
984
985 if !requested_link_id.is_empty() {
986 match self
987 .resolve_connection_by_link_id(&requested_link_id)
988 {
989 Err(err) => {
990 connection_success = false;
991 connection_error_msg = err;
992 }
993 Ok(Some(conn_id)) => {
994 existing_conn_for_link_id = true;
995 self.inner
996 .link_id_to_conn_id
997 .write()
998 .insert(requested_link_id.clone(), conn_id);
999 info!(
1000 link_id = %requested_link_id,
1001 conn_id,
1002 "Connection already exists for link_id"
1003 );
1004 }
1005 Ok(None) => {}
1006 }
1007 }
1008
1009 if connection_success && !existing_conn_for_link_id {
1010 match self
1011 .inner
1012 .message_processor
1013 .connect(client_config.clone(), None, None)
1014 .await
1015 {
1016 Err(e) => {
1017 connection_success = false;
1018 connection_error_msg =
1019 format!("Connection failed: {}", e);
1020 }
1021 Ok(conn_id) => {
1022 if !requested_link_id.is_empty() {
1023 self.inner
1024 .link_id_to_conn_id
1025 .write()
1026 .insert(
1027 requested_link_id.clone(),
1028 conn_id.1,
1029 );
1030 }
1031 info!(
1032 endpoint = %client_endpoint, "Successfully created connection",
1033 );
1034 }
1035 }
1036 }
1037 }
1038 }
1039
1040 connections_status.push(v1::ConnectionAck {
1042 link_id: conn.link_id.clone(),
1043 success: connection_success,
1044 error_msg: connection_error_msg,
1045 });
1046 }
1047
1048 for route in &config.routes_to_set {
1050 let mut route_success = true;
1051 let mut route_error_msg = String::new();
1052
1053 let conn = self.resolve_route_connection(route);
1054
1055 if let Ok(Some(conn)) = conn {
1056 let name = route.name.clone().unwrap();
1057 let source = name.clone().with_id(NameId::NULL_COMPONENT);
1058
1059 let msg = DataPlaneMessage::builder()
1060 .source(source.clone())
1061 .destination(name.clone())
1062 .identity("")
1063 .flags(SlimHeaderFlags::default().with_recv_from(conn))
1064 .build_subscribe()
1065 .unwrap();
1066
1067 match self.send_subscribe_message_with_ack(msg).await {
1068 Ok(subscription_id) => {
1069 self.inner
1071 .route_subscription_ids
1072 .lock()
1073 .insert((name.clone(), conn), subscription_id);
1074 info!(?route, "Successfully created route");
1075 }
1076 Err(err) => {
1077 route_success = false;
1078 route_error_msg =
1079 format!("Failed to subscribe: {}", err);
1080 }
1081 }
1082 } else {
1083 route_success = false;
1084 route_error_msg = match conn {
1085 Ok(None) => {
1086 format!(
1087 "Connection with link_id {} not found",
1088 route.link_id.as_deref().unwrap_or("<none>")
1089 )
1090 }
1091 Err(err) => err,
1092 _ => "unknown connection lookup error".to_string(),
1093 };
1094 }
1095
1096 routes_status.push(v1::RouteAck {
1098 route: Some(route.clone()),
1099 success: route_success,
1100 error_msg: route_error_msg,
1101 });
1102 }
1103
1104 for route in &config.routes_to_delete {
1106 let mut route_success = true;
1107 let mut route_error_msg = String::new();
1108
1109 let conn = self.resolve_route_connection(route);
1110
1111 if let Ok(Some(conn)) = conn {
1112 let name = route.name.clone().unwrap();
1113 let source = name.clone().with_id(NameId::NULL_COMPONENT);
1114
1115 let msg = DataPlaneMessage::builder()
1116 .source(source.clone())
1117 .destination(name.clone())
1118 .identity("")
1119 .flags(SlimHeaderFlags::default().with_recv_from(conn))
1120 .build_unsubscribe()
1121 .unwrap();
1122
1123 let sub_id = self
1124 .inner
1125 .route_subscription_ids
1126 .lock()
1127 .remove(&(name.clone(), conn));
1128 let unsubscribe_result = match sub_id {
1129 Some(subscription_id) => {
1130 self.send_unsubscribe_message_with_ack(
1131 msg,
1132 subscription_id,
1133 )
1134 .await
1135 }
1136 None => {
1137 let (ack_id, ack_rx) =
1143 self.inner.subscription_manager.register_ack();
1144 let mut fresh_msg = msg;
1145 fresh_msg.set_subscription_id(ack_id);
1146 if let Err(e) =
1147 self.send_control_message(fresh_msg).await
1148 {
1149 self.inner.subscription_manager.cancel_ack(ack_id);
1150 Err(format!("datapath send error: {}", e.chain()))
1151 } else {
1152 match tokio::time::timeout(
1153 SUBSCRIPTION_ACK_TIMEOUT,
1154 ack_rx,
1155 )
1156 .await
1157 {
1158 Ok(Ok(Ok(()))) => Ok(()),
1159 Ok(Ok(Err(err))) => Err(err.to_string()),
1160 Ok(Err(_)) => {
1161 Err("subscription ack channel closed"
1162 .to_string())
1163 }
1164 Err(_) => {
1165 self.inner
1166 .subscription_manager
1167 .cancel_ack(ack_id);
1168 Err("subscription ack timed out"
1169 .to_string())
1170 }
1171 }
1172 }
1173 }
1174 };
1175 if let Err(err) = unsubscribe_result {
1176 route_success = false;
1177 route_error_msg = format!("Failed to unsubscribe: {}", err);
1178 } else {
1179 info!(?route, "Successfully deleted route");
1180 }
1181 } else {
1182 route_success = false;
1183 route_error_msg = match conn {
1184 Ok(None) => {
1185 format!(
1186 "Connection with link_id {} not found",
1187 route.link_id.as_deref().unwrap_or("<none>")
1188 )
1189 }
1190 Err(err) => err,
1191 _ => "unknown connection lookup error".to_string(),
1192 };
1193 }
1194
1195 routes_status.push(v1::RouteAck {
1197 route: Some(route.clone()),
1198 success: route_success,
1199 error_msg: route_error_msg,
1200 });
1201 }
1202
1203 (connections_status, routes_status)
1204 };
1205
1206 let config_ack = v1::ConfigurationCommandAck {
1207 original_message_id: msg.message_id.clone(),
1208 connections_status,
1209 routes_status,
1210 };
1211
1212 let reply = ControlMessage {
1213 message_id: uuid::Uuid::new_v4().to_string(),
1214 payload: Some(Payload::ConfigCommandAck(config_ack)),
1215 };
1216
1217 if let Err(e) = tx.send(Ok(reply)).await {
1218 error!(error = %e.chain(), "failed to send ConfigurationCommandAck");
1219 }
1220
1221 info!(
1222 connections = %config.connections_to_create.len(),
1223 connections_to_delete = %config.connections_to_delete.len(),
1224 routes_to_set = %config.routes_to_set.len(),
1225 routes_to_del = %config.routes_to_delete.len(),
1226 "Processed ConfigurationCommand"
1227 );
1228 }
1229 Payload::RouteListRequest(_) => {
1230 const CHUNK_SIZE: usize = 100;
1231
1232 let conn_table = self.inner.message_processor.connection_table();
1233 let mut entries = Vec::new();
1234
1235 self.inner.message_processor.subscription_table().for_each(
1236 |name, id, local, remote, peer, edge| {
1237 let mut entry = RouteEntry {
1238 name: Some(name.clone().with_id(id)),
1239 ..Default::default()
1240 };
1241
1242 for &cid in local {
1243 entry.connections.push(ConnectionEntry {
1244 id: cid,
1245 connection_type: ConnectionType::Local as i32,
1246 config_data: "{}".to_string(),
1247 link_id: None,
1248 direction: ConnectionDirection::Outgoing as i32,
1249 peer_node_id: None,
1250 });
1251 }
1252
1253 let conn_slices = [
1254 (remote, ConnectionType::Remote),
1255 (peer, ConnectionType::Peer),
1256 (edge, ConnectionType::Edge),
1257 ];
1258 for (conns, ct) in conn_slices {
1259 for &cid in conns {
1260 if let Some(conn) = conn_table.get(cid) {
1261 entry.connections.push(ConnectionEntry {
1262 id: cid,
1263 connection_type: ct as i32,
1264 config_data: conn
1265 .config_data()
1266 .and_then(|d| serde_json::to_string(d).ok())
1267 .unwrap_or_else(|| "{}".to_string()),
1268 link_id: conn.link_id(),
1269 direction: if conn.is_outgoing() {
1270 ConnectionDirection::Outgoing as i32
1271 } else {
1272 ConnectionDirection::Incoming as i32
1273 },
1274 peer_node_id: conn
1275 .peer_node_id()
1276 .map(str::to_string),
1277 });
1278 } else {
1279 error!(%cid, ?ct, "no connection entry for id");
1280 }
1281 }
1282 }
1283 entries.push(entry);
1284 },
1285 );
1286
1287 let chunks: Vec<_> = entries.chunks(CHUNK_SIZE).collect();
1288 if chunks.is_empty() {
1289 let resp = ControlMessage {
1290 message_id: uuid::Uuid::new_v4().to_string(),
1291 payload: Some(Payload::RouteListResponse(RouteListResponse {
1292 original_message_id: msg.message_id.clone(),
1293 entries: vec![],
1294 done: true,
1295 })),
1296 };
1297 if let Err(e) = tx.send(Ok(resp)).await {
1298 error!(error = %e.chain(), "failed to send route list response");
1299 }
1300 } else {
1301 let n = chunks.len();
1302 for (i, chunk) in chunks.into_iter().enumerate() {
1303 let resp = ControlMessage {
1304 message_id: uuid::Uuid::new_v4().to_string(),
1305 payload: Some(Payload::RouteListResponse(RouteListResponse {
1306 original_message_id: msg.message_id.clone(),
1307 entries: chunk.to_vec(),
1308 done: i + 1 == n,
1309 })),
1310 };
1311 if let Err(e) = tx.send(Ok(resp)).await {
1312 error!(error = %e.chain(), "failed to send route batch");
1313 break;
1314 }
1315 }
1316 }
1317 }
1318 Payload::ConnectionListRequest(_) => {
1319 let mut all_entries = Vec::new();
1320 self.inner
1321 .message_processor
1322 .connection_table()
1323 .for_each(|id, conn| {
1324 let ct = match conn.connection_type() {
1325 slim_datapath::tables::ConnType::Local => ConnectionType::Local,
1326 slim_datapath::tables::ConnType::Remote => {
1327 ConnectionType::Remote
1328 }
1329 slim_datapath::tables::ConnType::Peer => ConnectionType::Peer,
1330 slim_datapath::tables::ConnType::Edge => ConnectionType::Edge,
1331 };
1332 all_entries.push(ConnectionEntry {
1333 id,
1334 connection_type: ct as i32,
1335 config_data: conn
1336 .config_data()
1337 .and_then(|d| serde_json::to_string(d).ok())
1338 .unwrap_or_else(|| "{}".to_string()),
1339 link_id: conn.link_id(),
1340 direction: if conn.is_outgoing() {
1341 ConnectionDirection::Outgoing as i32
1342 } else {
1343 ConnectionDirection::Incoming as i32
1344 },
1345 peer_node_id: conn.peer_node_id().map(str::to_string),
1346 });
1347 });
1348
1349 const CHUNK_SIZE: usize = 100;
1350 let chunks: Vec<_> = all_entries.chunks(CHUNK_SIZE).collect();
1351 if chunks.is_empty() {
1352 let resp = ControlMessage {
1353 message_id: uuid::Uuid::new_v4().to_string(),
1354 payload: Some(Payload::ConnectionListResponse(
1355 ConnectionListResponse {
1356 original_message_id: msg.message_id.clone(),
1357 entries: vec![],
1358 done: true,
1359 },
1360 )),
1361 };
1362 if let Err(e) = tx.send(Ok(resp)).await {
1363 error!(error = %e.chain(), "failed to send connection list response");
1364 }
1365 } else {
1366 let n = chunks.len();
1367 for (i, chunk) in chunks.into_iter().enumerate() {
1368 let resp = ControlMessage {
1369 message_id: uuid::Uuid::new_v4().to_string(),
1370 payload: Some(Payload::ConnectionListResponse(
1371 ConnectionListResponse {
1372 original_message_id: msg.message_id.clone(),
1373 entries: chunk.to_vec(),
1374 done: i + 1 == n,
1375 },
1376 )),
1377 };
1378 if let Err(e) = tx.send(Ok(resp)).await {
1379 error!(error = %e.chain(), "failed to send connection list batch");
1380 break;
1381 }
1382 }
1383 }
1384 }
1385 Payload::RegisterNodeRequest(_) => {
1386 error!("received a register node request");
1387 }
1388 Payload::DeregisterNodeRequest(_) => {
1389 error!("received a deregister node request");
1390 }
1391 _ => {
1392 debug!("received unsupported message type from control - ignoring");
1394 }
1395 }
1396 }
1397 None => {
1398 error!(
1399 message_id = %msg.message_id,
1400 "received control message with no payload",
1401 );
1402 }
1403 }
1404
1405 Ok(())
1406 }
1407
1408 async fn handle_subscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1409 let mut sub_vec = vec![];
1410
1411 let cmd = v1::Route {
1412 name: Some(dst),
1413 link_id: None,
1414 direction: None,
1415 };
1416
1417 debug!(
1418 "handle_subscribe_message: sending route_to_set to control plane: {:?}",
1419 cmd
1420 );
1421 sub_vec.push(cmd);
1422
1423 let ctrl = ControlMessage {
1424 message_id: uuid::Uuid::new_v4().to_string(),
1425 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1426 connections_to_create: vec![],
1427 connections_to_delete: vec![],
1428 routes_to_set: sub_vec,
1429 routes_to_delete: vec![],
1430 reconcile: false,
1431 connections_received: vec![],
1432 })),
1433 };
1434
1435 return self.send_or_queue_notification(ctrl, clients).await;
1436 }
1437
1438 async fn handle_link_received(
1441 &self,
1442 link: &slim_datapath::api::ProtoLink,
1443 clients: &[ClientConfig],
1444 ) {
1445 use slim_datapath::api::ProtoLinkType;
1446
1447 let link_id = match &link.link_type {
1448 Some(ProtoLinkType::LinkNegotiation(payload)) => &payload.link_id,
1449 _ => {
1450 debug!("handle_link_received: ignoring link message without negotiation payload");
1451 return;
1452 }
1453 };
1454
1455 if link_id.is_empty() {
1456 debug!("handle_link_received: ignoring link message with empty link_id");
1457 return;
1458 }
1459
1460 debug!(
1461 "handle_link_received: notifying control-plane of new link_id: {}",
1462 link_id
1463 );
1464
1465 let ctrl = ControlMessage {
1466 message_id: uuid::Uuid::new_v4().to_string(),
1467 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1468 connections_to_create: vec![],
1469 connections_to_delete: vec![],
1470 routes_to_set: vec![],
1471 routes_to_delete: vec![],
1472 reconcile: false,
1473 connections_received: vec![v1::ConnectionEntry {
1474 id: 0,
1475 connection_type: ConnectionType::Remote as i32,
1476 config_data: String::new(),
1477 link_id: Some(link_id.clone()),
1478 direction: ConnectionDirection::Incoming as i32,
1479 peer_node_id: None,
1480 }],
1481 })),
1482 };
1483
1484 self.send_or_queue_notification(ctrl, clients).await;
1485 }
1486
1487 async fn handle_unsubscribe_message(&self, dst: ProtoName, clients: &[ClientConfig]) {
1488 self.inner
1493 .route_subscription_ids
1494 .lock()
1495 .retain(|(name, _), _| *name != dst);
1496
1497 let mut unsub_vec = vec![];
1498
1499 let cmd = v1::Route {
1500 name: Some(dst),
1501 link_id: None,
1502 direction: None,
1503 };
1504
1505 debug!(
1506 "handle_unsubscribe_message: sending route_to_delete to control plane: {:?}",
1507 cmd
1508 );
1509 unsub_vec.push(cmd);
1510
1511 let ctrl = ControlMessage {
1512 message_id: uuid::Uuid::new_v4().to_string(),
1513 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1514 connections_to_create: vec![],
1515 connections_to_delete: vec![],
1516 routes_to_set: vec![],
1517 routes_to_delete: unsub_vec,
1518 reconcile: false,
1519 connections_received: vec![],
1520 })),
1521 };
1522
1523 return self.send_or_queue_notification(ctrl, clients).await;
1524 }
1525
1526 async fn send_subscribe_message_with_ack(
1528 &self,
1529 mut msg: DataPlaneMessage,
1530 ) -> Result<u64, String> {
1531 let (ack_id, ack_rx) = self.inner.subscription_manager.register_ack();
1532 msg.set_subscription_id(ack_id);
1533
1534 if let Err(e) = self.send_control_message(msg).await {
1535 self.inner.subscription_manager.cancel_ack(ack_id);
1536 return Err(format!("datapath send error: {}", e.chain()));
1537 }
1538
1539 match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1540 Ok(Ok(Ok(()))) => Ok(ack_id),
1541 Ok(Ok(Err(err))) => Err(err.to_string()),
1542 Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1543 Err(_) => {
1544 self.inner.subscription_manager.cancel_ack(ack_id);
1545 Err("subscription ack timed out".to_string())
1546 }
1547 }
1548 }
1549
1550 async fn send_unsubscribe_message_with_ack(
1552 &self,
1553 mut msg: DataPlaneMessage,
1554 subscription_id: u64,
1555 ) -> Result<(), String> {
1556 let ack_rx = self
1557 .inner
1558 .subscription_manager
1559 .register_ack_with_id(subscription_id);
1560 msg.set_subscription_id(subscription_id);
1561
1562 if let Err(e) = self.send_control_message(msg).await {
1563 self.inner.subscription_manager.cancel_ack(subscription_id);
1564 return Err(format!("datapath send error: {}", e.chain()));
1565 }
1566
1567 match tokio::time::timeout(SUBSCRIPTION_ACK_TIMEOUT, ack_rx).await {
1568 Ok(Ok(Ok(()))) => Ok(()),
1569 Ok(Ok(Err(err))) => Err(err.to_string()),
1570 Ok(Err(_)) => Err("subscription ack channel closed".to_string()),
1571 Err(_) => {
1572 self.inner.subscription_manager.cancel_ack(subscription_id);
1573 Err("subscription ack timed out".to_string())
1574 }
1575 }
1576 }
1577
1578 async fn send_control_message(&self, msg: DataPlaneMessage) -> Result<(), ControllerError> {
1580 self.inner.tx_slim.send(Ok(msg)).await.map_err(|e| {
1581 error!(error = %e.chain(), "error sending message into datapath");
1582 ControllerError::Datapath(slim_datapath::errors::DataPathError::ConnectionError)
1583 })
1584 }
1585
1586 async fn send_or_queue_notification(&self, ctrl_msg: ControlMessage, clients: &[ClientConfig]) {
1593 let mut sent = false;
1594
1595 for c in clients {
1596 let tx = match self.inner.tx_channels.read().get(&c.endpoint) {
1597 Some(tx) => tx.clone(),
1598 None => continue,
1599 };
1600
1601 match tx.try_send(Ok(ctrl_msg.clone())) {
1602 Ok(()) => {
1603 sent = true;
1604 }
1605 Err(mpsc::error::TrySendError::Full(_)) => {
1606 debug!(
1607 endpoint = %c.endpoint,
1608 "channel full, queuing notification instead of blocking"
1609 );
1610 }
1611 Err(mpsc::error::TrySendError::Closed(_)) => {
1612 debug!(
1613 endpoint = %c.endpoint,
1614 "channel closed, queuing notification"
1615 );
1616 }
1617 }
1618 }
1619
1620 if !sent {
1621 let mut queue = self.inner.pending_notifications.lock();
1622 if queue.len() >= MAX_QUEUED_NOTIFICATIONS {
1623 queue.pop_front();
1624 debug!("queue full, removed oldest notification");
1625 }
1626 queue.push_back(ctrl_msg);
1627 }
1628 }
1629
1630 fn drain_watch(&self) -> Result<drain::Watch, ControllerError> {
1632 self.inner
1633 .drain_watch
1634 .read()
1635 .clone()
1636 .ok_or(ControllerError::AlreadyStopped)
1637 }
1638
1639 async fn send_queued_notifications(
1641 &self,
1642 tx: &mpsc::Sender<Result<ControlMessage, Status>>,
1643 endpoint: &str,
1644 ) {
1645 let notifications = {
1646 let mut queue = self.inner.pending_notifications.lock();
1647 if queue.is_empty() {
1648 return;
1649 }
1650 queue.drain(..).collect::<Vec<_>>()
1651 };
1652
1653 if notifications.is_empty() {
1654 return;
1655 }
1656
1657 debug!(
1658 "sending {} queued subscription notifications to {}",
1659 notifications.len(),
1660 endpoint
1661 );
1662
1663 let mut failed_notifications = Vec::new();
1664 for notification in notifications {
1665 if let Err(e) = tx.send(Ok(notification)).await {
1666 error!(
1667 error = %e.chain(),
1668 %endpoint,
1669 "failed to send queued notification to control plane",
1670 );
1671
1672 failed_notifications.push(e.0.unwrap());
1674 }
1675 }
1676
1677 if !failed_notifications.is_empty() {
1679 self.inner
1680 .pending_notifications
1681 .lock()
1682 .extend(failed_notifications);
1683 }
1684 }
1685
1686 async fn replay_local_subscriptions(&self, clients: &[ClientConfig]) {
1689 let mut routes: Vec<v1::Route> = Vec::new();
1690 self.inner.message_processor.subscription_table().for_each(
1691 |name, id, local, _remote, _peer, edge| {
1692 if local.is_empty() && edge.is_empty() {
1693 return;
1694 }
1695 routes.push(v1::Route {
1696 name: Some(name.clone().with_id(id)),
1697 link_id: None,
1698 direction: None,
1699 });
1700 },
1701 );
1702
1703 if routes.is_empty() {
1704 return;
1705 }
1706
1707 info!(
1708 count = routes.len(),
1709 "replaying local subscriptions to control plane"
1710 );
1711
1712 let ctrl = ControlMessage {
1713 message_id: uuid::Uuid::new_v4().to_string(),
1714 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1715 connections_to_create: vec![],
1716 connections_to_delete: vec![],
1717 routes_to_set: routes,
1718 routes_to_delete: vec![],
1719 reconcile: false,
1720 connections_received: vec![],
1721 })),
1722 };
1723
1724 self.send_or_queue_notification(ctrl, clients).await;
1725 }
1726
1727 fn process_control_message_stream(
1729 &self,
1730 config: Option<ClientConfig>,
1731 mut stream: impl Stream<Item = Result<ControlMessage, Status>> + Unpin + Send + 'static,
1732 tx: mpsc::Sender<Result<ControlMessage, Status>>,
1733 cancellation_token: CancellationToken,
1734 ) -> Result<JoinHandle<()>, ControllerError> {
1735 let this = self.clone();
1736 let watch = self.drain_watch()?;
1737
1738 let handle = tokio::spawn(async move {
1739 let endpoint = config
1741 .as_ref()
1742 .map(|c| c.endpoint.clone())
1743 .unwrap_or_else(|| "unknown".to_string());
1744 info!(%endpoint, "connected to control plane");
1745
1746 let mut retry_connect = false;
1747
1748 let mut active_connections = Vec::new();
1749 this.inner
1750 .message_processor
1751 .connection_table()
1752 .for_each(|id, conn| {
1753 active_connections.push(v1::ConnectionEntry {
1754 id,
1755 connection_type: v1::ConnectionType::Remote as i32,
1756 config_data: match conn.config_data() {
1757 Some(data) => {
1758 serde_json::to_string(data).unwrap_or_else(|_| "{}".to_string())
1759 }
1760 None => "{}".to_string(),
1761 },
1762 link_id: conn.link_id(),
1763 direction: if conn.is_outgoing() {
1764 v1::ConnectionDirection::Outgoing as i32
1765 } else {
1766 v1::ConnectionDirection::Incoming as i32
1767 },
1768 peer_node_id: conn.peer_node_id().map(str::to_string),
1769 });
1770 });
1771
1772 let active_routes = {
1773 let conn_id_to_link_id: HashMap<u64, String> = this
1774 .inner
1775 .link_id_to_conn_id
1776 .read()
1777 .iter()
1778 .map(|(lid, cid)| (*cid, lid.clone()))
1779 .collect();
1780 this.inner
1781 .route_subscription_ids
1782 .lock()
1783 .iter()
1784 .map(|((name, conn_id), _sub_id)| v1::Route {
1785 name: Some(name.clone()),
1786 link_id: conn_id_to_link_id.get(conn_id).cloned(),
1787 direction: None,
1788 })
1789 .collect::<Vec<_>>()
1790 };
1791
1792 let register_request = ControlMessage {
1793 message_id: uuid::Uuid::new_v4().to_string(),
1794 payload: Some(Payload::RegisterNodeRequest(v1::RegisterNodeRequest {
1795 node_id: this.inner.id.clone(),
1796 group_name: this.inner.group_name.clone(),
1797 connection_details: this.inner.connection_details.clone(),
1798 connections: active_connections,
1799 routes: active_routes,
1800 })),
1801 };
1802
1803 if config.is_some()
1805 && let Err(e) = tx.send(Ok(register_request)).await
1806 {
1807 error!(error = %e.chain(), "failed to send register request");
1808 return;
1809 }
1810
1811 this.send_queued_notifications(&tx, &endpoint).await;
1814
1815 if config.is_some() {
1817 let registration_result = tokio::time::timeout(
1818 Duration::from_secs(10),
1819 async {
1820 loop {
1821 tokio::select! {
1822 next = stream.next() => {
1823 match next {
1824 Some(Ok(msg)) => {
1825 if let Some(Payload::RegisterNodeResponse(resp)) = msg.payload {
1826 if resp.success {
1827 info!(
1828 connections = resp.connections.len(),
1829 routes = resp.routes.len(),
1830 "registration acknowledged by control plane"
1831 );
1832 return Ok(resp);
1833 } else {
1834 return Err("registration rejected by control plane".to_string());
1835 }
1836 }
1837 }
1838 Some(Err(_)) => return Err("stream error waiting for registration response".to_string()),
1839 None => return Err("stream closed before registration response".to_string()),
1840 }
1841 }
1842 _ = cancellation_token.cancelled() => {
1843 return Err("cancelled while waiting for registration response".to_string());
1844 }
1845 }
1846 }
1847 }
1848 ).await;
1849
1850 match registration_result {
1851 Ok(Ok(resp)) => {
1852 if !resp.connections.is_empty() || !resp.routes.is_empty() {
1854 let init_cmd = ControlMessage {
1855 message_id: uuid::Uuid::new_v4().to_string(),
1856 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
1857 connections_to_create: resp.connections,
1858 routes_to_set: resp.routes,
1859 routes_to_delete: vec![],
1860 connections_to_delete: vec![],
1861 reconcile: true,
1862 connections_received: vec![],
1863 })),
1864 };
1865 if let Err(e) = this.handle_new_control_message(init_cmd, &tx).await {
1866 error!(error = %e.chain(), "failed to apply initial state from registration");
1867 }
1868 }
1869 }
1870 Ok(Err(e)) => {
1871 error!(%e, "registration handshake failed");
1872 return;
1873 }
1874 Err(_) => {
1875 error!("registration handshake timed out");
1876 return;
1877 }
1878 }
1879 }
1880
1881 if let Some(ref cfg) = config {
1883 this.replay_local_subscriptions(std::slice::from_ref(cfg))
1884 .await;
1885 }
1886
1887 let mut drain_fut = std::pin::pin!(watch.clone().signaled());
1888
1889 loop {
1890 tokio::select! {
1891 next = stream.next() => {
1892 match next {
1893 Some(Ok(msg)) => {
1894 if let Err(e) = this.handle_new_control_message(msg, &tx).await {
1895 error!(error = %e.chain(), "error processing incoming control message");
1896 }
1897 }
1898 Some(Err(e)) => {
1899 if let Some(io_err) = Self::match_for_io_error(&e) {
1900 if io_err.kind() == std::io::ErrorKind::BrokenPipe {
1901 info!("connection closed by peer");
1902 } else {
1903 error!(
1905 error = %e.chain(),
1906 io_error_kind = ?io_err.kind(),
1907 "IO error receiving control messages"
1908 );
1909 }
1910 } else {
1911 error!(error = %e.chain(), "error receiving control messages");
1913 }
1914
1915 retry_connect = true;
1916 break;
1917 }
1918 None => {
1919 debug!("end of stream");
1920 retry_connect = true;
1921 break;
1922 }
1923 }
1924 }
1925 _ = cancellation_token.cancelled() => {
1926 debug!("shutting down stream on cancellation token");
1927 break;
1928 }
1929 _ = &mut drain_fut => {
1930 debug!("shutting down stream on drain");
1931 break;
1932 }
1933 }
1934 }
1935
1936 info!(%endpoint, "control plane stream closed");
1937
1938 if retry_connect && let Some(config) = config {
1939 info!(%config.endpoint, "retrying connection to control plane");
1940 this.connect(config.clone(), cancellation_token)
1941 .await
1942 .map_or_else(
1943 |e| {
1944 error!(error = %e.chain(), "failed to reconnect to control plane");
1945 },
1946 |tx| {
1947 info!(%config.endpoint, "reconnected to control plane");
1948
1949 this.inner
1950 .tx_channels
1951 .write()
1952 .insert(config.endpoint.clone(), tx);
1953 },
1954 )
1955 }
1956 });
1957
1958 Ok(handle)
1959 }
1960
1961 async fn connect(
1965 &self,
1966 config: ClientConfig,
1967 cancellation_token: CancellationToken,
1968 ) -> Result<mpsc::Sender<Result<ControlMessage, Status>>, ControllerError> {
1969 info!(%config.endpoint, "connecting to control plane");
1970
1971 let mut remote_conn_ids: Vec<u64> = Vec::new();
1975 self.inner
1976 .message_processor
1977 .connection_table()
1978 .for_each(|id, conn| {
1979 if conn.is_outgoing() && matches!(conn.connection_type(), ConnType::Remote) {
1980 remote_conn_ids.push(id);
1981 }
1982 });
1983 for conn_id in &remote_conn_ids {
1984 if let Err(e) = self.inner.message_processor.disconnect(*conn_id) {
1985 debug!(conn_id, error = %e.chain(), "failed to disconnect remote connection");
1986 }
1987 }
1988 if !remote_conn_ids.is_empty() {
1989 info!(
1990 count = remote_conn_ids.len(),
1991 "disconnected remote connections for clean restart"
1992 );
1993 }
1994
1995 self.inner.route_subscription_ids.lock().clear();
1999 self.inner.link_id_to_conn_id.write().clear();
2000
2001 let watch = self.drain_watch()?;
2003
2004 let connect_fut = async {
2005 let channel = match config.to_channel().await? {
2006 TransportChannel::Grpc(c) => c,
2007 TransportChannel::Websocket(_) => {
2008 return Err(ControllerError::ConfigError(
2009 slim_config::errors::ConfigError::GrpcChannelUnsupportedTransport,
2010 ));
2011 }
2012 };
2013
2014 let mut client = ControllerServiceClient::new(channel.clone());
2015 let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2016 let out_stream = ReceiverStream::new(rx).filter_map(|res| match res {
2017 Ok(msg) => Some(msg),
2018 Err(e) => {
2019 error!(error = %e, "dropping outbound control message due to error");
2020 None
2021 }
2022 });
2023 let stream = client
2024 .open_control_channel(Request::new(out_stream))
2025 .await?;
2026 Ok((tx, stream))
2027 };
2028
2029 let (tx, stream) = tokio::select! {
2030 result = connect_fut => { result? }
2031 _ = cancellation_token.cancelled() => {
2032 debug!("connection cancelled during setup");
2033 return Err(ControllerError::Canceled);
2034 }
2035 _ = watch.signaled() => {
2036 debug!("drain signal received during connection setup");
2037 return Err(ControllerError::Canceled);
2038 }
2039 };
2040
2041 let endpoint_key = config.endpoint.clone();
2043 let handle = self.process_control_message_stream(
2044 Some(config),
2045 stream.into_inner(),
2046 tx.clone(),
2047 cancellation_token.clone(),
2048 )?;
2049 self.inner
2050 .stream_handles
2051 .lock()
2052 .insert(endpoint_key, handle);
2053
2054 Ok(tx)
2056 }
2057
2058 fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
2059 let mut err: &(dyn std::error::Error + 'static) = err_status;
2060
2061 loop {
2062 if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
2063 return Some(io_err);
2064 }
2065
2066 if let Some(h2_err) = err.downcast_ref::<h2::Error>()
2069 && let Some(io_err) = h2_err.get_io()
2070 {
2071 return Some(io_err);
2072 }
2073
2074 err = err.source()?;
2075 }
2076 }
2077}
2078
2079#[tonic::async_trait]
2080impl GrpcControllerService for ControllerService {
2081 type OpenControlChannelStream =
2082 Pin<Box<dyn Stream<Item = Result<ControlMessage, Status>> + Send + 'static>>;
2083
2084 async fn open_control_channel(
2085 &self,
2086 request: Request<tonic::Streaming<ControlMessage>>,
2087 ) -> Result<Response<Self::OpenControlChannelStream>, Status> {
2088 let remote_endpoint = request
2090 .remote_addr()
2091 .map(|addr| addr.to_string())
2092 .unwrap_or_else(|| "unknown".to_string());
2093
2094 let stream = request.into_inner();
2095 let (tx, rx) = mpsc::channel::<Result<ControlMessage, Status>>(128);
2096
2097 let cancellation_token = CancellationToken::new();
2098
2099 let handle = self
2101 .process_control_message_stream(None, stream, tx.clone(), cancellation_token.clone())
2102 .map_err(|e| {
2103 error!(error = %e.chain(), "error processing control message stream");
2104 Status::unavailable("failed to process control message stream")
2105 })?;
2106 self.inner
2107 .stream_handles
2108 .lock()
2109 .insert(remote_endpoint.clone(), handle);
2110
2111 self.inner
2112 .tx_channels
2113 .write()
2114 .insert(remote_endpoint.clone(), tx);
2115
2116 if let Some(old_token) = self
2117 .inner
2118 .cancellation_tokens
2119 .write()
2120 .insert(remote_endpoint.clone(), cancellation_token)
2121 {
2122 old_token.cancel();
2123 }
2124
2125 let out_stream = ReceiverStream::new(rx);
2126 Ok(Response::new(
2127 Box::pin(out_stream) as Self::OpenControlChannelStream
2128 ))
2129 }
2130}
2131
2132#[cfg(test)]
2133mod tests {
2134 use super::*;
2135 use tracing_test::traced_test;
2136
2137 async fn setup_control_planes(
2138 server_endpoint: &str,
2139 server_name: &str,
2140 client_name: &str,
2141 ) -> (ControlPlane, ControlPlane, ClientConfig) {
2142 let server_config = ServerConfig::with_endpoint(server_endpoint)
2143 .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
2144 let client_config = ClientConfig::with_endpoint(&format!("http://{}", server_endpoint))
2145 .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
2146
2147 let message_processor_server = MessageProcessor::new();
2148 let message_processor_client = MessageProcessor::new();
2149
2150 let control_plane_server = ControlPlane::new(ControlPlaneSettings {
2151 id: server_name.to_string(),
2152 group_name: None,
2153 servers: vec![server_config.clone()],
2154 clients: vec![],
2155 message_processor: message_processor_server,
2156 connection_details: vec![from_server_config(&server_config)],
2157 });
2158
2159 let control_plane_client = ControlPlane::new(ControlPlaneSettings {
2160 id: client_name.to_string(),
2161 group_name: None,
2162 servers: vec![],
2163 clients: vec![client_config.clone()],
2164 message_processor: message_processor_client,
2165 connection_details: vec![],
2166 });
2167
2168 (control_plane_server, control_plane_client, client_config)
2169 }
2170
2171 #[tokio::test]
2172 #[traced_test]
2173 async fn test_end_to_end() {
2174 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2175 setup_control_planes(
2176 "127.0.0.1:50051",
2177 "test-server-instance",
2178 "test-client-instance",
2179 )
2180 .await;
2181
2182 control_plane_server.run().await.unwrap();
2183 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2184 control_plane_client.run().await.unwrap();
2185 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2186
2187 assert!(logs_contain("received a register node request"));
2188 }
2189
2190 #[tokio::test]
2191 #[traced_test]
2192 async fn test_subscription_notification_queue_drain() {
2193 let (mut control_plane_server, mut control_plane_client, client_config) =
2195 setup_control_planes(
2196 "127.0.0.1:50061",
2197 "queue-drain-server",
2198 "queue-drain-client",
2199 )
2200 .await;
2201
2202 let controller = control_plane_client.controller.clone();
2203 assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2204
2205 const N: usize = 5;
2206 for i in 0..N {
2207 let ctrl_msg = ControlMessage {
2208 message_id: uuid::Uuid::new_v4().to_string(),
2209 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2210 connections_to_create: vec![],
2211 connections_to_delete: vec![],
2212 routes_to_set: vec![v1::Route {
2213 name: Some(
2214 ProtoName::from_strings(["queued", "sub", &format!("name-{i}")])
2215 .with_id(i as u128),
2216 ),
2217 link_id: None,
2218 direction: None,
2219 }],
2220 routes_to_delete: vec![],
2221 reconcile: false,
2222 connections_received: vec![],
2223 })),
2224 };
2225 controller
2226 .send_or_queue_notification(ctrl_msg, std::slice::from_ref(&client_config))
2227 .await;
2228 }
2229 assert_eq!(controller.inner.pending_notifications.lock().len(), N);
2230
2231 control_plane_server.run().await.expect("server run failed");
2232 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2233 control_plane_client.run().await.expect("client run failed");
2234 tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
2235
2236 assert_eq!(controller.inner.pending_notifications.lock().len(), 0);
2237 assert!(
2238 logs_contain(&format!("sending {} queued subscription notifications", N)),
2239 "Expected log about sending queued subscription notifications"
2240 );
2241
2242 drop(controller);
2243 drop(control_plane_server);
2244 drop(control_plane_client);
2245 }
2246
2247 #[tokio::test]
2248 #[traced_test]
2249 async fn test_delete_connection_by_link_id_success_ack() {
2250 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2251 setup_control_planes(
2252 "127.0.0.1:50081",
2253 "delete-linkid-server",
2254 "delete-linkid-client",
2255 )
2256 .await;
2257
2258 control_plane_server.run().await.unwrap();
2259 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2260 control_plane_client.run().await.unwrap();
2261 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2262
2263 let controller = control_plane_client.controller.clone();
2264 let link_id = "test-delete-link-id".to_string();
2265
2266 let (tx, _rx) = tokio::sync::mpsc::channel(16);
2268 let conn = slim_datapath::connection::Connection::new(
2269 slim_datapath::tables::ConnType::Remote,
2270 slim_datapath::connection::Channel::Server(tx),
2271 )
2272 .with_negotiation(&link_id, "1.0.0");
2273 controller
2274 .inner
2275 .message_processor
2276 .forwarder()
2277 .on_connection_established(conn, None)
2278 .unwrap();
2279
2280 let ctrl_msg = ControlMessage {
2281 message_id: uuid::Uuid::new_v4().to_string(),
2282 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2283 connections_to_create: vec![],
2284 connections_to_delete: vec![link_id.clone()],
2285 routes_to_set: vec![],
2286 routes_to_delete: vec![],
2287 reconcile: false,
2288 connections_received: vec![],
2289 })),
2290 };
2291 let (tx, mut rx) = mpsc::channel(1);
2292 controller
2293 .handle_new_control_message(ctrl_msg, &tx)
2294 .await
2295 .expect("config command must be handled");
2296
2297 let ack_msg = rx
2298 .recv()
2299 .await
2300 .expect("expected ack message")
2301 .expect("ack should be ok");
2302 let ack = match ack_msg.payload {
2303 Some(Payload::ConfigCommandAck(ack)) => ack,
2304 _ => panic!("expected ConfigCommandAck payload"),
2305 };
2306 assert_eq!(ack.connections_status.len(), 1);
2307 assert_eq!(ack.connections_status[0].link_id, link_id);
2308 assert!(ack.connections_status[0].success);
2309 }
2310
2311 #[tokio::test]
2312 #[traced_test]
2313 async fn test_delete_connection_by_link_id_unknown_fails_ack() {
2314 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2315 "127.0.0.1:50082",
2316 "delete-linkid-server-unknown",
2317 "delete-linkid-client-unknown",
2318 )
2319 .await;
2320
2321 let controller = control_plane_client.controller.clone();
2322 let ctrl_msg = ControlMessage {
2323 message_id: uuid::Uuid::new_v4().to_string(),
2324 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2325 connections_to_create: vec![],
2326 connections_to_delete: vec!["unknown-link-id".to_string()],
2327 routes_to_set: vec![],
2328 routes_to_delete: vec![],
2329 reconcile: false,
2330 connections_received: vec![],
2331 })),
2332 };
2333 let (tx, mut rx) = mpsc::channel(1);
2334 controller
2335 .handle_new_control_message(ctrl_msg, &tx)
2336 .await
2337 .expect("config command must be handled");
2338
2339 let ack_msg = rx
2340 .recv()
2341 .await
2342 .expect("expected ack message")
2343 .expect("ack should be ok");
2344 let ack = match ack_msg.payload {
2345 Some(Payload::ConfigCommandAck(ack)) => ack,
2346 _ => panic!("expected ConfigCommandAck payload"),
2347 };
2348 assert_eq!(ack.connections_status.len(), 1);
2349 assert_eq!(ack.connections_status[0].link_id, "unknown-link-id");
2350 assert!(!ack.connections_status[0].success);
2351 assert!(ack.connections_status[0].error_msg.contains("not found"));
2352
2353 drop(control_plane_server);
2354 }
2355
2356 #[tokio::test]
2357 #[traced_test]
2358 async fn test_create_connection_with_existing_link_id_reuses_connection_ack() {
2359 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2360 setup_control_planes(
2361 "127.0.0.1:50083",
2362 "create-linkid-server",
2363 "create-linkid-client",
2364 )
2365 .await;
2366
2367 control_plane_server.run().await.unwrap();
2368 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2369 control_plane_client.run().await.unwrap();
2370 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2371
2372 let controller = control_plane_client.controller.clone();
2373 let link_id = "test-create-link-id".to_string();
2374
2375 let (tx, _rx) = tokio::sync::mpsc::channel(16);
2377 let conn = slim_datapath::connection::Connection::new(
2378 slim_datapath::tables::ConnType::Remote,
2379 slim_datapath::connection::Channel::Server(tx),
2380 )
2381 .with_negotiation(&link_id, "1.0.0");
2382 controller
2383 .inner
2384 .message_processor
2385 .forwarder()
2386 .on_connection_established(conn, None)
2387 .unwrap();
2388
2389 let endpoint = "http://127.0.0.1:59999";
2390 let connection_config = serde_json::json!({
2391 "endpoint": endpoint,
2392 "link_id": link_id
2393 })
2394 .to_string();
2395
2396 let ctrl_msg = ControlMessage {
2397 message_id: uuid::Uuid::new_v4().to_string(),
2398 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2399 connections_to_create: vec![v1::Connection {
2400 link_id: "reuse-existing-link".to_string(),
2401 config_data: connection_config,
2402 }],
2403 connections_to_delete: vec![],
2404 routes_to_set: vec![],
2405 routes_to_delete: vec![],
2406 reconcile: false,
2407 connections_received: vec![],
2408 })),
2409 };
2410
2411 let (tx, mut rx) = mpsc::channel(1);
2412 controller
2413 .handle_new_control_message(ctrl_msg, &tx)
2414 .await
2415 .expect("config command must be handled");
2416
2417 let ack_msg = rx
2418 .recv()
2419 .await
2420 .expect("expected ack message")
2421 .expect("ack should be ok");
2422 let ack = match ack_msg.payload {
2423 Some(Payload::ConfigCommandAck(ack)) => ack,
2424 _ => panic!("expected ConfigCommandAck payload"),
2425 };
2426 assert_eq!(ack.connections_status.len(), 1);
2427 assert_eq!(ack.connections_status[0].link_id, "reuse-existing-link");
2428 assert!(ack.connections_status[0].success);
2429
2430 assert!(
2431 controller
2432 .inner
2433 .link_id_to_conn_id
2434 .read()
2435 .contains_key(&link_id),
2436 "expected link_id to be mapped to reused connection id"
2437 );
2438 }
2439
2440 #[tokio::test]
2441 #[traced_test]
2442 async fn test_subscription_set_unknown_link_id_fails_ack() {
2443 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2444 "127.0.0.1:50084",
2445 "sub-linkid-server-unknown",
2446 "sub-linkid-client-unknown",
2447 )
2448 .await;
2449
2450 let controller = control_plane_client.controller.clone();
2451 let ctrl_msg = ControlMessage {
2452 message_id: uuid::Uuid::new_v4().to_string(),
2453 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2454 connections_to_create: vec![],
2455 connections_to_delete: vec![],
2456 routes_to_set: vec![v1::Route {
2457 name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2458 link_id: Some("missing-link-id".to_string()),
2459 direction: None,
2460 }],
2461 routes_to_delete: vec![],
2462 reconcile: false,
2463 connections_received: vec![],
2464 })),
2465 };
2466 let (tx, mut rx) = mpsc::channel(1);
2467 controller
2468 .handle_new_control_message(ctrl_msg, &tx)
2469 .await
2470 .expect("config command must be handled");
2471
2472 let ack_msg = rx
2473 .recv()
2474 .await
2475 .expect("expected ack message")
2476 .expect("ack should be ok");
2477 let ack = match ack_msg.payload {
2478 Some(Payload::ConfigCommandAck(ack)) => ack,
2479 _ => panic!("expected ConfigCommandAck payload"),
2480 };
2481
2482 assert_eq!(ack.routes_status.len(), 1);
2483 assert!(!ack.routes_status[0].success);
2484 assert!(
2485 ack.routes_status[0]
2486 .error_msg
2487 .contains("Connection with link_id missing-link-id not found")
2488 );
2489
2490 drop(control_plane_server);
2491 }
2492
2493 #[tokio::test]
2494 #[traced_test]
2495 async fn test_create_connection_invalid_config_fails_ack() {
2496 let (_control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2497 "127.0.0.1:50085",
2498 "create-invalid-config-server",
2499 "create-invalid-config-client",
2500 )
2501 .await;
2502
2503 let controller = control_plane_client.controller.clone();
2504 let ctrl_msg = ControlMessage {
2505 message_id: uuid::Uuid::new_v4().to_string(),
2506 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2507 connections_to_create: vec![v1::Connection {
2508 link_id: "invalid-config-conn".to_string(),
2509 config_data: "{invalid-json".to_string(),
2510 }],
2511 connections_to_delete: vec![],
2512 routes_to_set: vec![],
2513 routes_to_delete: vec![],
2514 reconcile: false,
2515 connections_received: vec![],
2516 })),
2517 };
2518 let (tx, mut rx) = mpsc::channel(1);
2519 controller
2520 .handle_new_control_message(ctrl_msg, &tx)
2521 .await
2522 .expect("config command must be handled");
2523
2524 let ack_msg = rx
2525 .recv()
2526 .await
2527 .expect("expected ack message")
2528 .expect("ack should be ok");
2529 let ack = match ack_msg.payload {
2530 Some(Payload::ConfigCommandAck(ack)) => ack,
2531 _ => panic!("expected ConfigCommandAck payload"),
2532 };
2533 assert_eq!(ack.connections_status.len(), 1);
2534 assert!(!ack.connections_status[0].success);
2535 assert!(
2536 ack.connections_status[0]
2537 .error_msg
2538 .contains("Failed to parse config")
2539 );
2540 }
2541
2542 #[tokio::test]
2543 #[traced_test]
2544 async fn test_subscription_delete_unknown_link_id_fails_ack() {
2545 let (control_plane_server, control_plane_client, _client_cfg) = setup_control_planes(
2546 "127.0.0.1:50086",
2547 "sub-del-linkid-server-unknown",
2548 "sub-del-linkid-client-unknown",
2549 )
2550 .await;
2551
2552 let controller = control_plane_client.controller.clone();
2553 let ctrl_msg = ControlMessage {
2554 message_id: uuid::Uuid::new_v4().to_string(),
2555 payload: Some(Payload::ConfigCommand(v1::ConfigurationCommand {
2556 connections_to_create: vec![],
2557 connections_to_delete: vec![],
2558 routes_to_set: vec![],
2559 routes_to_delete: vec![v1::Route {
2560 name: Some(ProtoName::from_strings(["org", "ns", "agent"]).with_id(1u128)),
2561 link_id: Some("missing-link-id-delete".to_string()),
2562 direction: None,
2563 }],
2564 reconcile: false,
2565 connections_received: vec![],
2566 })),
2567 };
2568 let (tx, mut rx) = mpsc::channel(1);
2569 controller
2570 .handle_new_control_message(ctrl_msg, &tx)
2571 .await
2572 .expect("config command must be handled");
2573
2574 let ack_msg = rx
2575 .recv()
2576 .await
2577 .expect("expected ack message")
2578 .expect("ack should be ok");
2579 let ack = match ack_msg.payload {
2580 Some(Payload::ConfigCommandAck(ack)) => ack,
2581 _ => panic!("expected ConfigCommandAck payload"),
2582 };
2583
2584 assert_eq!(ack.routes_status.len(), 1);
2585 assert!(!ack.routes_status[0].success);
2586 assert!(
2587 ack.routes_status[0]
2588 .error_msg
2589 .contains("Connection with link_id missing-link-id-delete not found")
2590 );
2591
2592 drop(control_plane_server);
2593 }
2594
2595 #[tokio::test]
2596 #[traced_test]
2597 async fn test_shutdown_drains_resources() {
2598 let (mut control_plane_server, mut control_plane_client, _client_cfg) =
2600 setup_control_planes(
2601 "127.0.0.1:50071",
2602 "shutdown-server-instance",
2603 "shutdown-client-instance",
2604 )
2605 .await;
2606
2607 control_plane_server
2609 .run()
2610 .await
2611 .expect("server should start");
2612 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2613 control_plane_client
2614 .run()
2615 .await
2616 .expect("client should start");
2617 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
2618
2619 let server_tokens_before = control_plane_server
2621 .controller
2622 .inner
2623 .cancellation_tokens
2624 .read()
2625 .len();
2626 assert!(
2627 server_tokens_before > 0,
2628 "expected server to have active cancellation tokens before shutdown"
2629 );
2630
2631 let client_tokens_before = control_plane_client
2632 .controller
2633 .inner
2634 .cancellation_tokens
2635 .read()
2636 .len();
2637 assert!(
2638 client_tokens_before > 0,
2639 "expected client to have active cancellation tokens before shutdown"
2640 );
2641
2642 control_plane_client
2644 .shutdown()
2645 .await
2646 .expect("client shutdown ok");
2647 control_plane_server
2648 .shutdown()
2649 .await
2650 .expect("server shutdown ok");
2651
2652 let server_tokens_after = control_plane_server
2654 .controller
2655 .inner
2656 .cancellation_tokens
2657 .read()
2658 .len();
2659 assert_eq!(
2660 server_tokens_after, 0,
2661 "expected server cancellation tokens to be drained after shutdown"
2662 );
2663
2664 let client_tokens_after = control_plane_client
2665 .controller
2666 .inner
2667 .cancellation_tokens
2668 .read()
2669 .len();
2670 assert_eq!(
2671 client_tokens_after, 0,
2672 "expected client cancellation tokens to be drained after shutdown"
2673 );
2674
2675 assert!(
2677 control_plane_server.shutdown().await.is_err(),
2678 "second shutdown on server should return an error"
2679 );
2680 assert!(
2681 control_plane_client.shutdown().await.is_err(),
2682 "second shutdown on client should return an error"
2683 );
2684 }
2685
2686 #[tokio::test]
2687 #[traced_test]
2688 async fn test_shutdown_without_run() {
2689 let (control_plane_server, mut _control_plane_client, _client_cfg) = setup_control_planes(
2691 "127.0.0.1:50072",
2692 "shutdown-no-run-server",
2693 "shutdown-no-run-client",
2694 )
2695 .await;
2696
2697 assert_eq!(
2699 control_plane_server
2700 .controller
2701 .inner
2702 .cancellation_tokens
2703 .read()
2704 .len(),
2705 0,
2706 "expected zero cancellation tokens before shutdown when not run"
2707 );
2708
2709 control_plane_server
2711 .shutdown()
2712 .await
2713 .expect("shutdown without prior run should succeed");
2714
2715 assert_eq!(
2717 control_plane_server
2718 .controller
2719 .inner
2720 .cancellation_tokens
2721 .read()
2722 .len(),
2723 0,
2724 "expected zero cancellation tokens after shutdown when not run"
2725 );
2726
2727 assert!(
2729 control_plane_server.shutdown().await.is_err(),
2730 "second shutdown should error due to missing drain signal"
2731 );
2732 }
2733}